Creating and appending files in
theFileSystemmodule
Tokickthingsoffthough,wewillstartwiththeFileSystemmodule.We'llgo
throughtheprocessofcreatingafileandappendingtoit:
Whenyouviewadocspageforabuilt-inmodule,whetherit'sFileSystemora
different module, you'll see a long list of all the different functions and
properties that you have available to you. The one we'll use in this section is
fs.appendFile.
Ifyouclickonit,itwilltakeyoutothespecificdocumentation,andthisiswhere
wecanfigureouthowtouseappendFile,asshowninthefollowingscreenshot:
Now,appendFileisprettysimple.We'llpasstoittwostringarguments(shownin
theprecedingscreenshot):
Onewillbethefilename
Theotherwillbethedatawewanttoappendtothefile
Thisis allwe needto provideinorder tocall fs.appendFile.Before wecan call
fs.appendFile,weneedtorequireit.Thewholepointofrequiringistoletusload
inothermodules.Inthiscase,we'llloadinthefsmodulefromapp.js.
Let'screateavariablethatwillbeaconstant,usingconst.
Since we'll not be manipulating the code the module sends back,
there's no need to use the var keyword; we will use the const
keyword.
Then we'll give it a name, fs and set it equal to require(), as shown in the
followingcode:
constfs=require()
Here,require() is a function that's available to you inside any of your Node.js
files.Youdon'thavetodoanythingspecialtocallit,yousimplycallitasshown
intheprecedingcode.Insidetheargumentlist,we'lljustpassonestring.
Now,everytimeyoucallrequire(),whetheryou'reloadingabuilt-in
module,athird-partymodule,oryourownfile,youjustpassinone
string.
Inourcase,we'llpassinthemodulename,whichisfsandtossinasemicolonat
theend,asshowninthefollowingcode:
constfs=require('fs');
ThiswilltellNodethatyouwanttofetchallofthecontentsofthefsmoduleand
storetheminthefsvariable.Atthispoint,wehaveaccesstoallofthefunctions
available on the fs module, which we explored over in the docs, including
fs.appendFile.
Backin Atom,wecan calltheappendFileby callingfs.appendFile, passing inthe
two arguments that we'll use; the first one will be the filename, so we add
greetings.txt,andthesecondonewillbethetextyouwanttoappendtothefile.
Inourcase,we'llappendHelloworld!,asshowninthefollowingcode:
fs.appendFile('greetings.txt','Helloworld!');
Let'ssavethefile,asshownintheprecedingcommand,andrunitfromTerminal
toseewhathappens.
WarningwhenrunningtheprogramonNodev7
If you're running Node v7 or greater, you'll get a little warning
when you run the program inside Terminal. Now, on v7, it'll still
work, it's just a warning, but you can get rid of it using the
followingcode:
//Orignalline
fs.appendFile('greetings.txt','Helloworld!');
//Optionone
fs.appendFile('greetings.txt','Helloworld!',function(err){
if(err){
console.log('Unabletowritetofile');
}
});
//Optiontwo
fs.appendFileSync('greetings.txt','Helloworld!');
Intheprecedingcode,wehavetheoriginallinethatwehaveinsideourprogram.
InOption onehere isto add acallback asthe thirdargumenttothe appendfile.
This callback will get executed when either an error occurs or the file
successfullygetswrittentoo.Insideoptionone,wehaveanifstatement;ifthere
isanerror,wesimplyprintamessagetothescreen,Unabletowritetofile.
Now,oursecondoptionintheprecedingcode,Optiontwo,istocallappendFileSync,
whichisasynchronousmethod(we'lltalkmoreabout thatlater);this function
doesnottakethethirdargument.Youcantypeitasshownintheprecedingcode
andyouwon'tgetthewarning.
So,pickoneofthesetwooptionsifyouseethewarning;bothwillworkmuch
thesame.
Ifyouareonv6,youcanstickwiththetheoriginalline,shownatthetopofthe
preceding code,although you might as well useone of the two options below
thatlinetomakeyourcodealittlemorefutureproof.
Fearnot,we'llbetalkingaboutasynchronousandsynchronousfunctions,aswell
as callback functions, extensively throughout the book. What I'm giving you
hereinthecodeisjust atemplate,something youcan writeinyour fileto get
that error removed. In a few chapters, you will understand exactly what these
twomethodsareandhowtheywork.
IfwedotheappendingoverinTerminal,nodeapp.js,we'llseesomethingpretty
cool:
As shown in the preceding code, we get our one console.log statement, Starting
app.. So we know the app started correctly. Also, if we head over into Atom,
we'll actually see that there's a brand new greetings.txt file, as shown in the
followingcode.Thisisthetextfilethatwascreatedbyfs.appendFile:
console.log('Startingapp.');
constfs=require('fs');
fs.appendFile('greetings.txt','Helloworld!');
Here,fs.appendFiletriestoappendgreetings.txttoafile;ifthefiledoesn'texist,it
simplycreatesit:
You can see that we have our message, Hello world! in the greetings.txt file,
printingtothescreen.Injustafewminutes,wewereabletoloadinabuilt-in
Nodemoduleandcallafunctionthatletsuscreateabrandnewfile.
If we call it again by rerunning the command using the up arrow key and the
enterkey,andwe headback intothe contentsofgreetings.txt, youcan seethis
timearoundthatwehaveHelloworld!twice,asshownhere:
ItappendedHelloworld!onetimeforeachtimewerantheprogram.Wehavean
applicationthatcreatesabrandnewfileonourfilesystem,andifthefilealready
exists,itsimplyaddstoit.
TheOSmoduleinrequire()
Once we have created and appended the greetings.txt file, we'll customize this
greeting.txt file. To do this, we'll explore one more built-in module. We'll be
usingmorethanjustappendFile in the future.We'll be exploringother methods.
Forthissection,therealgoalistounderstandrequire().Therequire()functionlets
usloadinthemodule'sfunctionalitysothatwecancallit.
The second module that we'll be using is OS, and we can view it in the
documentation. In the OS module, we'll use the method defined at the very
bottom,os.userInfo([options]):
The os.userInfo([options]) method gets called and returns various information
aboutthecurrentlylogged-inuser,suchastheusername,andthisiswhatwe'll
pulloff:
UsingtheusernamethatcomesfromtheOS,wecancustomizethegreeting.txt
filesothatinsteadofHelloworld!itcansayHelloGary!.
To get started, we have to require OS. This means that we'll go back inside
Atom.Now,justbelowwhereIcreatedmyfsconstant,I'llcreateanewconstant
calledos,settingitequaltorequire();thisgetscalledasafunctionandpassesone
argument,themodulename,os,asshownhere:
console.log('Startingapp.');
constfs=require('fs');
constos=require('os');
fs.appendFile('greetings.txt','Helloworld!');
From here, we can start calling methods available on the OS module, such as
os.userInfo([optional]).
Let'smakeanewvariablecalledusertostoretheresult. Thevariableuser will
getsetequaltoos.userInfo,andwecancalluserInfowithoutanyarguments:
console.log('Startingapp.');
constfs=require('fs');
constos=require('os');
varuser=os.userInfo();
fs.appendFile('greetings.txt','Helloworld!');
Now,before wedo anythingwiththe fs.appendFileline,I'll commentitout and
printthecontentsoftheuservariableusingconsole.log:
console.log('Startingapp.');
constfs=require('fs');
constos=require('os');
varuser=os.userInfo();
console.log(user);
//fs.appendFile('greetings.txt','Helloworld!');
This will let us explore exactly what we get back. Over in Terminal, we can
rerunourprogramusingtheup arrowkey andenter key, andrighthere inthe
followingcode,youseethatwehaveanobjectwithafewproperties:
We have uid,gid,username,homedir, and shell. Depending on your OS, you'll not
haveallof these,butyou shouldalwayshave theusernameproperty. This isthe
onewecareabout.
ThismeansthatbackinsideAtom,we canuse user.usernameinsideofappendFile.
I'llremovetheconsole.logstatementanduncommentourcalltofs.appendFile:
console.log('Startingapp.');
constfs=require('fs');
constos=require('os');
varuser=os.userInfo();
fs.appendFile('greetings.txt','Helloworld!');
Now, where we have world in the fs.appendFile, we'll swap it with user.username.
Therearetwowayswecandothis.
Concatenatinguser.username
The first way is to remove world! and concatenate user.username. Then we can
concatenateanotherstringusingthe+(plus)operator,asshowninthefollowing
code:
console.log('Startingapp.');
constfs=require('fs');
constos=require('os');
varuser=os.userInfo();
fs.appendFile('greetings.txt','Hello'+user.username+'!');
Nowifwerunthis,everythingisgoingtoworkasexpected.OverinTerminal,
wecanrerunourapp.ItprintsStartingapp:
Overinthegreetings.txtfile,youshouldseesomethinglikeHelloGary!printingto
thescreen,asshownhere:
Using the fs module and the os module, we were able to grab the user's
username,createanewfile,andstoreit.
Usingtemplatestrings
Thesecondwaytoswapworldwithuser.usernameinthefs.appendFile is, using an
ES6featureknownastemplatestrings.Templatestringsstartandendwiththe`
(tick)operator,whichisavailabletotheleftofthe1keyonyourkeyboard.Then
youtypethingsasyounormallywould.
This means that we'll first type hello, then we'll add a space with the !
(exclamation)mark,andjustbefore!,wewillputthename:
console.log('Startingapp.');
constfs=require('fs');
constos=require('os');
varuser=os.userInfo();
fs.appendFile('greetings.txt',`Hello!`);
ToinsertaJavaScriptvariableinsideyourtemplatestring,youusethe$(dollar)
signfollowedbyopeningandclosingcurlybraces.Thenwewilljustreferencea
variablesuchasuser.username:
console.log('Startingapp.');
constfs=require('fs');
constos=require('os');
varuser=os.userInfo();
fs.appendFile('greetings.txt',`Hello${user.username}!`);
NoticethattheAtomeditoractuallypicksuponthesyntaxofcurly
braces.
Thisisallittakestousetemplatestrings;it'sanES6featureavailablebecause
you'reusingNodev6.Thissyntaxismucheasiertounderstandandupdatethan
thestring/concatenationversionwesawearlier.
Ifyourunthecode,itwillproducetheexactsameoutput.Wecanrunit,view
thetextfile,andthistimearound,wehaveHelloGary!twice,whichiswhatwe
wanthere:
Withthisinplace,wearenowdonewithourverybasicexampleandwe'reready
tostartcreatingourownfilesforournotesapplicationandrequiringtheminside
app.jsinthenextsection.
Firstup,youlearnedthatwecanuserequiretoloadinmodules.Thisletsustake
existingfunctionalitywrittenbyeithertheNodedevelopers,athird-partylibrary,
orourselves,andloaditintoafilesothatitcanbereusable.Creatingreusable
codeis essentialforbuilding large apps.Ifyou have to buildeverything in an
app every time, no one would everget anything done because they would get
stuckatbuildingthebasics,thingssuchasHTTPserversandwebservers.There
are already modules for such stuff, and we'll be taking advantage of the great
npmcommunity.Inthiscase,weusedtwobuilt-inmodules,fsandos.Weloaded
them in using require and we stored the module results inside two variables.
Thesevariablesstoreeverythingavailabletousfromthemodule;inthecaseof
fs, we use the appendFile method, and in the case of OS, we use the userInfo
method. Together, we were able to grab the username and save it into a file,
whichisfantastic.
Requireownfiles
Inthissection,youwilllearnhowtouserequire()toloadinotherfilesthatyou
createdinsideyourproject.Thiswillletyoumovefunctionsoutsideapp.jsinto
more specific files; this will make your application easier to scale, test, and
update.Togetstarted,thefirstthingwe'lldoistomakeanewfile.
Making a new file to load other
files
In the context of our notes app, the new file will store various functions for
writing and reading notes. As of now, you don't need to worry about that
functionality,aswe'llgetintothedetaillaterinthesection,butwewillcreatethe
filewhereitwilleventuallylive.Thisfilewillbenotes.js,andwe'llsaveitinside
therootofourapplication,rightalongsideapp.jsandgreetings.txt,asshownhere:
Forthemoment,allwe'lldoinsidenotesistouseconsole.logtoprintalittlelog
showingthefilehasbeenexecutedusingthefollowingcode:
console.log('Startingnotes.js');
Now, we have console.log on the top of notes and one on the top of app.js. I'll
changeconsole.login the app.js from Starting app. to Starting app.js. With this in
place,wecannowrequirethenotesfile.Itdoesn'texportanyfunctionality,but
that'sfine.
Bythe way, whenIsay export,I meanthenotes filedoesn't have
anyfunctionsorpropertiesthatanotherfilecantakeadvantageof.
We'lllookathowtoexportstufflaterinthesection.Fornowthough,we'llload
ourmoduleinmuchthesamewayweloadedinthebuilt-inNodemodules.
Let'smakeconst;I'llcallthisonenotesandsetitequaltothereturnresultfrom
require():
console.log('Startingapp.js');
constfs=require('fs');
constos=require('os');
constnotes=require('');
varuser=os.userInfo();
fs.appendFile('greetings.txt',`Hello${user.username}!`);
Insidetheparentheses,wewillpassinoneargumentthatwillbeastring,butit
willbealittledifferent.Intheprevioussection,wetypedinthemodulename,
butwhatwehaveinthiscaseisnotamodule,butafile,notes.js.Whatweneed
todoistotellNodewherethatfilelivesusingarelativepath.
Now,relativepathsstartwith./(adotforwardslash),whichpointstothecurrent
directory that the file is in. In this case, this points us to the app.js directory,
whichistherootofour projectnotes-node.Fromhere, wedon't haveto gointo
anyotherfolderstoaccessnotes.js,it'sintherootofourproject,sowecantype
itsname,asshowninthefollowingcode:
console.log('Startingapp.js');
constfs=require('fs');
constos=require('os');
constnotes=require('./notes.js');
varuser=os.userInfo();
fs.appendFile('greetings.txt',`Hello${user.username}!`);
Withthisinplace,wecannowsaveapp.jsandseewhathappenswhen werun
ourapplication.I'llruntheappusingthenodeapp.jscommand:
As shown in the preceding code output, we get our two logs. First, we get
Startingapp.jsandthenwegetStartingnotes.js.Now,Startingnotes.jscomesfrom
thenote.jsfile,anditonlyrunsbecausewerequiredthefileinsideofapp.js.
Commentoutthiscommandlinefromtheapp.jsfile,asshownhere:
console.log('Startingapp.js');
constfs=require('fs');
constos=require('os');
//constnotes=require('./notes.js');
varuser=os.userInfo();
fs.appendFile('greetings.txt',`Hello${user.username}!`);
Save the file, and rerun it from Terminal; you can see the notes.js file never
executesbecauseweneverexplicitlytouchit.
We never call it inside Terminal as we do in the preceding example, and we
neverrequire.
Fornowthough,wewillberequiringit,soI'lluncommentthatline.
Bytheway,I'musingcommand/(forwardslash)tocommentand
uncommentlinesquickly.This isakeyboard shortcutavailable in
most text editors; if you're on Windows or Linux, it might not be
command,itmightbeCtrlorsomethingelse.
Exportingfilesfromnote.jstouse
inapp.js
Fornowthough,thefocuswill beto exportsomethingfrom notes.jswhichwe
canuseinapp.js.Insidenotes.js(actually,insideallofourNodefiles),wehave
accesstoavariablecalledmodule.I'lluseconsole.logtoprintmoduletothescreenso
thatwecanexploreitoverinTerminal,asshownhere:
console.log('Startingnotes.js');
console.log(module);
Let'srerunthefiletoexploreit.Asshowninthefollowingscreenshot,wegeta
prettybigobject,thatis,differentpropertiesrelatedtothenotes.jsfile:
Now,totellthetruth,we'llnotbeusingmostoftheseproperties.Wehavethings
suchasid,exports,parent,andfilename.Theonlyonepropertywe'lleveruseinthis
bookisexports.
The exports object on the module property and everything on this object gets
exported.Thisobjectgetssetastheconstvariable,notes.Thismeansthatwecan
setpropertiesonit,theywillgetsetonnotes,andwecanusetheminsideapp.js.
A simple example of the working
oftheexportsobject
Let's take a quick look at how that works. What we'll do is to define an age
propertyusingmodule.exports,theobjectwejustexploredoverinTerminal.Also,
we know that it's an object because we can see it in the preceding screenshot
(exports:{});thismeansthatIcanaddaproperty,age,andsetitequaltomyage,
whichis25,asshownhere:
console.log('Startingnotes.js');
module.exports.age=25;
ThenIcansavethisfileandmoveintoapp.jstotakeadvantageofthisnewage
property.Theconstvariablenoteswillbestoringallofmyexports,inthepresent
case,justage.
In fs.appendFile, after the greeting.txt file, I'll add You are followed by the age.
Insidetemplatestrings,wewilluse$withcurlybraces,notes.age,andaperiodat
theend,asshownhere:
console.log('Startingapp.js');
constfs=require('fs');
constos=require('os');
constnotes=require('./notes.js');
varuser=os.userInfo();
fs.appendFile('greetings.txt',`Hello${user.username}!Youare${notes.age}.`);
NowourgreetingshouldsayHelloGary! Youare25.It'sgettingthe25valuefrom
ourseparatefile(thatis,note.js),whichisfantastic.
Let'stakea quickmomenttorerun theprogram overinTerminalusing theup
arrowkeyandenterkeys:
Back inside the app, we can open greetings.txt, and as shown in the following
screenshot,wehaveHelloGary!Youare25:
Usingrequire(),wewereabletorequireafilethatwecreated,andthisfilestored
somepropertiesthatwereadvantageoustotherestoftheproject.
Exportingthefunctions
Now, obviously, the preceding example is pretty contrived. We'll not be
exportingstaticnumbers;therealgoalofexportsistobeabletoexportfunctions
thatgetusedinsideapp.js.Let'stakeaquickmomenttoexporttwofunctions.In
the notes.js file, I'll set module.exports.addnote equal to a function; the function
keywordfollowedbyopeningandclosingparentheses,whichisfollowedbythe
curlybraces:
console.log('Startingnotes.js');
module.exports.addNote=function(){
}
Now,throughoutthecourse,I'llbeusingarrowfunctionswhereIcan,asshown
intheprecedingcode.ToconvertaregularES5functionintoanarrowfunction,
allyou do is remove the function keyword and replace it with an => sign right
betweentheparenthesesandtheopeningcurlybraces,asshownhere:
console.log('Startingnotes.js');
module.exports.addNote=()=>{
}
Now, there aresomemore subtletiestoarrow functions thatwe'll
be talking about throughout the book, but if you have an
anonymous function, you can swap it with an arrow function
withoutanyproblems.Thebigdifferenceisthatthearrowfunction
isnot goingto bindthe() => {} keyword or the arguments array,
whichwe'llbeexploringthroughoutthebook.Soifyoudogetsome
errors,it'sgoodtoknowthatthearrowfunctioncouldbethecause.
Fornowthough,we'llkeepthingsreallysimple,usingconsole.logtoprintaddNote.
Thiswillletusknowthatthe addNotefunctionwas called.We'llreturnastring,
'Newnote',asshownhere:
console.log('Startingnotes.js');
module.exports.addNote=()=>{
console.log('addNote');
return'Newnote';
};
Now,theaddNotefunctionisbeingdefinedinnotes.js,butwecantakeadvantage
ofitoverinapp.js.
Let's take a quick second to comment out both the appendFile and user line in
app.js:
console.log('Startingapp.js');
constfs=require('fs');
constos=require('os');
constnotes=require('./notes.js');
//varuser=os.userInfo();
//
//fs.appendFile('greetings.txt',`Hello${user.username}!Youare${notes.age}.`);
I'll add a variable, call the result, (res for short), and set it equal to the return
resultfromnotes.addNote:
console.log('Startingapp.js');
constfs=require('fs');
constos=require('os');
constnotes=require('./notes.js');
varres=notes.addNote();
//varuser=os.userInfo();
//
//fs.appendFile('greetings.txt',`Hello${user.username}!Youare${notes.age}.`);
Now,theaddNote function is a dummy function for the moment. It doesn't take
anyargumentsanditdoesn'tactuallydoanything,sowecancallitwithoutany
arguments.
Then we'll print the result variable, as shown in the following code, and we
wouldexpecttheresultvariabletobeequaltotheNewnotestring:
console.log('Startingapp.js');
constfs=require('fs');
constos=require('os');
constnotes=require('./notes.js');
varres=notes.addNote();
console.log(res);
//varuser=os.userInfo();
//
//fs.appendFile('greetings.txt',`Hello${user.username}!Youare${notes.age}.`);
IfIsave bothofmy files(app.js and notes.js) and rerun things from Terminal,
youcanseethatNewnoteprintstothescreenattheveryendandjustbeforeaddNote
prints:
ThismeansthatwesuccessfullyrequiredthenotesfilewecalledaddNote,andits
returnresultwassuccessfullyreturnedtoapp.js.
Using this exact pattern, we'll be able to define our functions for adding and
removingnotesoverinournotes.jsfile,butwe'llbeabletocallthemanywhere
insideofourapp,includinginapp.js.
Exercise–addinganewfunction
totheexportobject
Now it's time for a quick challenge. What I'd like you to do is make a new
functioninnotes.jscalledadd.Thisaddfunctionwillgetsetontheexportsobject.
Remember,exportsisanobject,soyoucansetmultipleproperties.
Thisadd function will take two arguments,a and b; it'll add them together and
returntheresult.Thenoverinapp.js,I'dlikeyoutocallthataddfunction,passing
intwonumbers,whateveryoulike,suchas9and-2,thenprinttheresulttothe
screenandmakesureitworkscorrectly.
YoucangetstartedbyremovingthecalltoaddNotesincethiswillnot
beneededforthechallenge.
So,takeamoment,createthataddfunctioninsidenotes.js,callitinsideapp.js,and
make sure the proper result prints to the screen. How'd it go? Hopefully, you
wereabletomakethatfunctionandcallitfromapp.js.
Solutiontotheexercise
Thefirststepintheprocesswillbetodefinethenewfunction.Innotes.js,I'llset
module.exports.addequaltothatfunction,asshownhere:
console.log('Startingnotes.js');
module.exports.addNote=()=>{
console.log('addNote');
return'Newnote';
};
module.exports.add=
Let's set it equal to an arrow function. If you used a regular function, that is
perfectly fine, I just prefer using the arrow function when I can. Also, inside
parentheses,wewillbegettingtwoarguments,we'llbegettingaandb,asshown
here:
console.log('Startingnotes.js');
module.exports.addNote=()=>{
console.log('addNote');
return'Newnote';
};
module.exports.add=(a,b)=>{
};
Allweneedtodoisreturntheresult,whichisreallysimple.Sowe'llenterreturn
a+b:
console.log('Startingnotes.js');
module.exports.addNote=()=>{
console.log('addNote');
return'Newnote';
};
module.exports.add=(a,b)=>{
returna+b;
};
Now, this was the first part of your challenge, defining a utility function in
notes.js;thesecondpartwastoactuallyuseitoverinapp.js.
Inapp.js,wecanuseourfunctionbyprintingtheconsole.logresultwithacolon:
(thisisjustforformatting).Asthesecondargument,we'llprinttheactualresults,
notes.add.Then,we'll adduptwo numbers;we'lladd 9and -2,as shownin this
code:
console.log('Startingapp.js');
constfs=require('fs');
constos=require('os');
constnotes=require('./notes.js');
console.log('Result:',notes.add(9,-2));
//varuser=os.userInfo();
//
//fs.appendFile('greetings.txt',`Hello${user.username}!Youare${notes.age}.`);
Theresultinthiscaseshouldbe7.Ifweruntheprogramyoucanseethatweget
justthat,7printstothescreen:
Ifyouwereabletogetthis,congratulations,yousuccessfullycompletedoneof
your first challenges. These challenges will be sprinkled throughout the book
and they'll get progressively more complex. But don't worry, we'll keep the
challenges pretty explicit; I'll tell you exactly what I want and exactly how I
wantitdone.Now,youcanplayaroundwithdifferentwaystodoit,therealgoal
is to just get you writing code independent of following someone else's lead.
Thatiswherethereallearninghappens.
Inthenextsection,wewillexplorehowtousethird-partymodules.Fromthere,
we'llstartbuildingthenotesapplication.
Third-partymodules
You now know two out of the three ways to use require(), and in this section,
we'llexplore the lastway, whichis to requirea packageyou've installed from
npm.AsImentionedinthefirstchapter,npmisabigpartofwhatmakesNode
so fantastic. There is a huge community of developers that have created
thousandsofpackagesthatalreadysolvesomeofthemostcommonproblemsin
Node applications. We will be taking advantage of quite a few packages
throughoutthebook.
Creating projects using npm
modules
Now,inthenpmpackages,there'snothingmagical,it'sregularNodecodethat
aimstosolveaspecificproblem.Thereasonyou'dwanttouseitissoyoudon't
havetospendallyourtimewritingtheseutilityfunctionsthatalreadyexist;not
onlydotheyexist,they'vebeentested,they'vebeenproventowork,andothers
haveusedthemanddocumentedthem.
Now,withallthatsaid,howdowegetstarted?Well,togetstarted,weactually
havetorunacommandfromtheTerminaltotellourapplicationwewanttouse
npmmodules.ThiscommandwillberunoverintheTerminal.Makesureyou've
navigated inside your project folder and inside the notes-node directory. Now,
whenyouinstalledNode,youalsoinstalledsomethingcallednpm.
Atonepoint,npmstoodforNodepackagemanager,butthat'snow
arunningjokebecausethereareplentyofthingsonnpmthatare
notspecifictoNode.Alotoffrontendframeworks,suchasjQuery
andreact,nowliveonnpmaswell,sothey'veprettymuchditched
theNodepackagemanagerexplanationandnowontheirsite,they
cyclethroughabunchofhilariousthingsthathappentomatchup
withnpm.
We will be running some npm commands and you can test that you have it
installedbyrunningnpm,aspace,and-v(we'rerunningnpmwiththevflag).This
shouldprinttheversion,asshowninthefollowingcode:
It's okay if your version is slightly different, that's not important; what is
importantisthatyouhavenpminstalled.
Now, we'll run a command called npm init in Terminal. This command will
prompt us to fill out a few questions about our npm project. We can run the
command and we can cycle through the questions, as shown in the following
screenshot:
Intheprecedingscreenshot,atthetopisaquickdescriptionofwhat'shappening,
anddownbelowit'llstartaskingyouafewquestions,asshowninthefollowing
screenshot:
Thequestionsincludethefollowing:
name: Your name can't have uppercase characters or spaces; you can use
notes-node,forexample.Youcanhitentertousethedefaultvalue,whichis
inparentheses.
version:1.0.0 worksfine too; wewill leavemost ofthese attheir default
value.
description:Wecanleavethisemptyatthemoment.
entrypoint:Thiswillbeapp.js,makesurethatshowsupproperly.
testcommand:We'llexplore testinglaterin thebook, sofor now,we can
leavethisempty.
gitrepository:We'llleavethatemptyfornowaswell.
keywords: These are used for searching for modules. We'll not be
publishingthismodulesowecanleavethoseempty.
author:Youmightaswelltypeyourname.
license:Forthelicense,we'llstickwithISCatthemoment;sincewe'renot
publishingit,itdoesn'treallymatter.
Afteransweringthesequestions,ifwehitenter,we'llgetthefollowingon our
screenandafinalquestion:
Now,Iwanttodispelthemyththatthiscommandisdoinganythingmagical.All
thiscommandisdoingiscreatingasinglefileinsideyourproject.It'llbeinthe
rootoftheprojectandit'scalledpackage.json,andthefilewilllookexactlylike
theprecedingscreenshot.
Tothefinalquestion,asshowndownbelowintheprecedingimage,youcanhit
enterortypeyestoconfirmthatthisiswhatyouwanttodo:
Nowthatwehavecreatedthefile,wecanactuallyviewitinsideourproject.As
showninthefollowingcode,wehavethepackage.jsonfile:
{
"name":"notes-node",
"version":"1.0.0",
"description":"",
"main":"app.js",
"scripts":{
"test":"echo\"Error:notestspecified\"&&exit1"
},
"author":"",
"license":"ISC"
}
And this is all it is, it's a simple description of your application. Now, as I
mentioned,we'llnotbepublishingourapptonpm,soalotofthisinformation
really isn't important to us. What is important, though, is that package.json is
wherewedefinethethird-partymoduleswewanttoinstallinourapplication.
Installing the lodash module in
ourapp
Toinstallamoduleintheapp,wewillrunacommandoverintheTerminal.In
thischapter,we'llbeinstallingamodulecalledlodash.Thelodashmodulecomes
withatonofutilitymethodsandfunctionsthatmakedevelopinginsideNodeor
JavaScript a heck of a lot easier. To take a look at what exactly we're getting
into,let'smoveintothebrowser.
We'lltogotohttps://www.npmjs.com.Thenwe'llsearchforthepackage,lodash,and
youcanseeitcomesup,asshowninthefollowingscreenshot:
Whenyouclickonit,youshouldbetakentothepackagepage,andthepackage
pagewillshowyoualotofstatisticsaboutthemoduleandthedocumentation,as
shownhere:
Now,IusethelodashpackagepagewhenI'mlookingfornewmodules;Iliketo
seehowmanydownloadsithasandwhenitwaslastupdated.Onthepackage
page,youcanseeitwasupdatedrecently,whichisgreatitmeansthepackageis
most likely compatible with the latest versions of Node, and if yougo further
down the page, you can see this is actually one of the most popular npm
packages,withoveramilliondownloadsaday.Wewillbeusingthismoduleto
explorehowtoinstallnpmmodulesandhowtoactuallyusetheminaproject.
Installationoflodash
Toinstalllodash,thefirstthingyouneedtograbisjustamodulename,whichis
lodash.Onceyouhavethatinformation,you'rereadytoinstallit.
Coming to Terminal, we'll run the npm install command. After installing, we'll
specify the module, lodash. Now, this command alone would work; what we'll
alsodo,though,isprovidethesaveflag.
Thenpminstalllodashcommandwillinstallthemodule,andthesaveflag,--(two)
hyphens followed bythe word save, will update the contents of the package.json
file.Let'srunthiscommand:
npminstallloadsh--save
Theprecedingcommandwillgoofftothenpmserversandfetchthecodeand
installitinsideyourproject,andanytimeyouinstallannpmmodule,it'lllivein
yourprojectinanode_modulesfolder.
Now,ifyouopenthatnode_modulesfolder,you'llseethelodashfolderasshownin
thefollowingcode.Thisisthemodulethatwejustinstalled:
{
"name":"notes-node",
"version":"1.0.0",
"description":"",
"main":"app.js",
"scripts":{
"test":"echo\"Error:notestspecified\"&&exit1"
},
"author":"",
"license":"ISC",
"dependencies":{
"lodash":"^4.17.4"
}
}
Asyoucanseeoverinpackage.jsonintheprecedingfigure,we'vealsohadsome
updatesautomaticallytakeplace.There'sanewdependenciesattributethathasan
objectwithkeyvaluepairs,wherethekeyisthemodulewewanttouseinour
projectandthevalueistheversionnumber,inthiscase,themostrecentversion,
version 4.17.4. With this in place, we can now require our module inside the
project.
Overinsideapp.js,wecantakeadvantageofeverythingthatcomesinlodashby
goingthrough the sameprocess of requiringit. We'll makea const, we'll name
thatconst_,(whichisacommonnameforthelodashutilitylibrary),andwe'llset
itequaltorequire().Insidetherequireparentheses,we'llpassinthemodulename
exactlyasitappearsinthepackage.jsonfile.Thisisthe samemodulename you
usedwhenyourannpminstall.Then,we'lltypelodash,asshownhere:
console.log('Startingapp.js');
constfs=require('fs');
constos=require('os');
const_=require('lodash');
constnotes=require('./notes.js');
console.log('Result:',notes.add(9,-2));
//varuser=os.userInfo();
//
//fs.appendFile('greetings.txt',`Hello${user.username}!Youare${notes.age}.`);
Now,theorderofoperationsisprettyimportanthere.Nodewillfirstlookfora
core module with the name lodash. It'll not find one because there is no core
module,sothenextplaceitwilllookisthenode_modulesfolder.Asshowninthe
following code, it will find lodash and load that module, returning any of the
exportsitprovides:
console.log('Startingapp.js');
constfs=require('fs');
constos=require('os');
const_=require('lodash');
constnotes=require('./notes.js');
console.log('Result:',notes.add(9,-2));
//varuser=os.userInfo();
//
//fs.appendFile('greetings.txt',`Hello${user.username}!Youare${notes.age}.`);
Usingtheutilitiesoflodash
Withtheexportsinplace,wecannowtakeadvantageofsomeoftheutilitiesthat
come with Lodash. We'll quickly explore two in this section, and we'll be
exploringmorethroughoutthebooksinceLodashisbasicallyjustasetofreally
handyutilities.Beforewedo,weshouldtakealookatthedocumentationsowe
knowexactlywhatwe'regettinginto.
This is a really common step when you're using an npm module:
first, you install it; second, you've got to look at those docs and
makesurethatyoucangetdonewhatyouwanttogetdone.
Onthenpmpage,clickthelodashlinkgiventhere,orgotolodash.comandclick
theAPIDocumentationpage,asshownhere:
Youcanviewallofthevariousmethodsyouhaveavailabletoyou,asshownin
thefollowingscreenshot:
Inourcase,we'llbeusingcommand+F(Ctrl+FforWindowsusers)tosearch
for_.isString. Then in the docs, we can click on it, opening it up in the main
page,asshowninthefollowingscreenshot:
The_.isStringisautilitythatcomeswithlodash,anditreturnstrueifthevariable
youpassinisastring,anditreturnsfalseifthevalueyoupassinisnotastring.
AndwecanprovethatbyusingitoverinAtom.Let'susethis.
Usingthe_.isStringutility
Tousethe_.isStringutility,we'lladdconsole.loginapp.jstoshowtheresulttothe
screenandwe'lluse_.isString, passingin a coupleofvalues. Let'spass in true
first, then we can duplicate this line and we'll pass in a string such as Gary, as
shownhere:
console.log('Startingapp.js');
constfs=require('fs');
constos=require('os');
const_=require('lodash');
constnotes=require('./notes.js');
console.log(_.isString(true));
console.log(_.isString('Gary'));
//console.log('Result:',notes.add(9,-2));
//varuser=os.userInfo();
//
//fs.appendFile('greetings.txt',`Hello${user.username}!Youare${notes.age}.`);
We can run our project over in the Terminal using the same command we've
usedpreviously,nodeapp.js,torunourfile:
Whenwerunthefile,wegetourtwopromptsthatwe'vestartedbothfiles,and
wegetfalseandthentrue.falsecomesbecausetheBooleanisnotastring,and
truecomesupbecauseGaryis indeedastring, soitpassesthe testof_.isString.
Thisisoneofthemanyutilityfunctionsthatcomesbundledwithlodash.
Now,lodashcandoalotmorethansimpletypechecking.Itcomeswithabunch
ofotherutilitymethodswecantakeadvantageof.Let'sexploreonemoreutility.
Using_.uniq
Back inside the browser,we can use command+F again to search for a new
utility,whichis_.uniq:
This unique method, simply takes an array and it returns that array with all
duplicatesremoved.Thatmeans ifIhavethe samenumberafew timesorthe
samestring,it'llremoveanyduplicates.Let'srunthis.
BackinsideAtom,wecanaddthisutilityintoourproject,we'llcommentoutour
_.isStringcallsandwewillmakeavariablecalledfilteredArray.Thiswillbethe
arraywithouttheduplicates,andwhatwe'lldoiscall,aftertheequalsign,_.uniq.
Now,asweknow,thistakesanarray.Andsincewe'retryingtousetheunique
function,we'llpassinanarraywithsomeduplicates.Useyournametwiceasa
string;I'llusemynameonce,followedbythenumber1,followedbymyname
again.ThenIcanuse1,2,3,and4asshownhere:
console.log('Startingapp.js');
constfs=require('fs');
constos=require('os');
const_=require('lodash');
constnotes=require('./notes.js');
//console.log(_.isString(true));
//console.log(_.isString('Gary'));
varfilteredArray=_.uniq(['Gary',1,'Gary',1,2,3,4]);
console.log();
//console.log('Result:',notes.add(9,-2));
//varuser=os.userInfo();
//
//fs.appendFile('greetings.txt',`Hello${user.username}!Youare${notes.age}.`);
Now, if things go as planned, we should get an array with all the duplicates
removed,which means we'll have one instance of Gary, one instance of 1, and
then2,3,and4,whichdon'thaveduplicates.
Thelastthingtodoistoprintthatusingconsole.logsowecanviewitinsidethe
Terminal. I'll pass in this filteredArray variable to our console.log statement as
showninthefollowingcode:
console.log('Startingapp.js');
constfs=require('fs');
constos=require('os');
const_=require('lodash');
constnotes=require('./notes.js');
//console.log(_.isString(true));
//console.log(_.isString('Gary'));
varfilteredArray=_.uniq(['Gary',1,'Gary',1,2,3,4]);
console.log(filteredArray);
//console.log('Result:',notes.add(9,-2));
//varuser=os.userInfo();
//
//fs.appendFile('greetings.txt',`Hello${user.username}!Youare${notes.age}.`);
Fromhere,wecanrunourprojectinsideNode.I'llusethelastcommand,thenI
canpress the enter key, and you can see we get our array with all duplicates
removed,asshowninthefollowingcodeoutput:
WehaveoneinstanceofthestringGary,oneinstanceofthenumber1,andthen
wehave2,3,4,exactlywhatweexpected.
Thelodash utility really is endless. There are so many functions that it can be
kind of overwhelming to explore at first, but as you start creating more
JavaScript and Node projects, you'll find yourself solving a lot of the same
problems over and over again when it comes to sorting, filtering, or type
checking,andinthatcase,it'sbesttouseautilitysuchaslodashtogetthatlifting
done.Thelodashutilityisgreatforthefollowingreasons:
Youdon'thavetokeeprewritingyourmethods
Itiswelltestedandithasbeentriedinproduction
Iftherewereanyissues,they'vebeensortedoutbynow.
Thenode_modulesfolder
Nowthatyouknowhowtouseathird-partymodule,thereisonemorethingI
wantto discuss. Thatis the node_modules folder in general. When you take your
NodeprojectandyouputitonGitHub,oryou'recopyingitaroundorsendingit
toafriend,thenode_modulesfolderreallyshouldn'tbetakenwithyou.
Thenode_modulesfoldercontainsgenerated code.Thisis notcode you'vewritten
and you should never make any updates to the files inside Node modules
becausethere'saprettygoodchancethey'llgetoverwrittennexttimeyouinstall
somemodules.
In our case, we've already defined the modules and the versions inside
package.jsonasshowninthefollowingcodebecauseweusedthathandysaveflag:
{
"name":"notes-node",
"version":"1.0.0",
"description":"",
"main":"app.js",
"scripts":{
"test":"echo\"Error:notestspecified\"&&exit1"
},
"author":"",
"license":"ISC",
"dependencies":{
"lodash":"^4.17.4"
}
}
This actually means we can delete the node_modules folder completely. Now, we
cancopythefolderandgiveittoafriend,wecanputitonGitHub,orwhatever
wewanttodo.Whenwewanttogetthatnode_modulesfolderback,allwehaveto
doinsidetheTerminalisrunthenpminstallcommandwithoutanymodulenames
oranyflags.
Thiscommand,whenrunwithoutanynames orflags, isgoing toload inyour
package.jsonfile,graballofthedependenciesandinstallthem.Afterrunningthis
command,thenode_modulesfolderisgoingtolookexactlyasitlookedbeforewe
deleted it. Now, when you are using Git and GitHub, instead of deleting the
node_modulesfolder,you'lljustignoreitfromyourrepository.
Now, what we have explored so far is a process we'll be going through a lot
morethroughoutthebook.Soifnpmstillseemsforeignoryou'renotquitesure
why it's even useful, it will become clear as we do more with our third-party
modules,ratherthanjusttypecheckingorlookingforuniqueitemsinanarray.
There'satonofpowerbehindthenpmcommunityandwe'llbeharnessingthat
toourfullestaswemakereal-worldapps.
Globalmodules
OneofthemajorcomplaintsIgetisthefactthatstudentshavetorestarttheapp
fromtheTerminaleverytimetheywanttoseethechangestheyjustmadeinside
theirtexteditor.So,inthissection,we'lltakealookathowwecanautomatically
restartourappaswemakechangestothefile.ThatmeansifIchangefromGary
toMikeandsaveit,itwillautomaticallyrestartoverintheTerminal.
Installingthenodemonmodule
Now,toautomaticallyrestartourappaswemakechangestoafile,wehaveto
installacommand-lineutility,andwe'lldothisusingnpm.Togetstarted,we'll
gotoGoogleChrome(orthebrowseryouareusing)andheadovertohttps://www.
npmjs.com, as we did previously in the Installing the lodash module in our app
section,andthemodulewe'relookingforiscallednodemon.
The nodemon will be responsible for watching our app for changes and
restarting the app when those changes occur. Right here, as we see in the
followingscreenshot,wecanviewthedocsfornodemonaswellasvariousother
thingssuchascurrentversionnumbersandsoon:
You will also notice that it's a really popular module, with over 30,000
downloadsaday.Now,thismoduleisalittledifferentfromtheoneweusedin
thelastsection,thatis,lodash.Thelodashgotinstalledandaddedintoourproject's
package.jsonfileasshowninthefollowingcodeblock:
{
"name":"notes-node",
"version":"1.0.0",
"description":"",
"main":"app.js",
"scripts":{
"test":"echo\"Error:notestspecified\"&&exit1"
},
"author":"",
"license":"ISC",
"dependencies":{
"lodash":"^4.17.4"
}
}
Thatmeansitwentintoournode_modulesfolderandwewereabletorequireitin
ourapp.jsfile(refertotheprevioussectionformoredetail).Nodemon,however,
worksalittledifferently.It'sacommand-lineutilitythatgetsexecutedfromthe
Terminal. It will be a completely new way of starting our application, and to
installmodulestoberunfromthecommandline,wehavetotweaktheinstall
commandthatweusedinthelastsection.
Fornow,wecanstartoffmuchthesameway,though.We'llusenpminstalland
type the name just like we did in the Installingthelodash module in our app
section,butinsteadofusingthesaveflag,we'llusethegflag,whichisshortfor
global,asshownhere:
npminstallnodemon-g
Thiscommandinstallsnodemonasaglobalutilityonyourmachine,whichmeans
it'll not get added to your specific project and you'll never require nodemon.
Instead,you'llberunningthenodemoncommandfromTerminal,asshownhere:
When we install nodemon using the preceding command, it'll go off to npm and
fetchallofthecodethatcomeswithnodemon.
Andit'lladditintotheinstallationwhereNodeandnpmliveonyourmachine,
outsidetheprojectyou'reworkingon.
Thenpm install nodemon -g command could be executed from anywhere in your
machine;itdoesnotneedtobeexecutedfromtheprojectfoldersinceitdoesn't
actually update the project at all. With this in place, though, we now have a
brandnewcommandonourmachine,nodemon.
Executingnodemon
NodemonwillgetexecutedasNodedid,wherewetypethecommandandthen
wetypethe filewewant to start.In our case, app.jsis the root ofour project.
Whenyourunit,you'llseeafewthings,asshownhere:
We'll see a combination of our app's output, along with nodemon logs that show
youwhat'shappening.Asshownintheprecedingcode,youcanseetheversion
nodemonisusing,thefilesit'swatching,andthecommanditactuallyran.Now,at
thispoint,it'swaitingformorechanges;italreadyranthroughtheentireappand
it'llkeeprunninguntilanotherchangehappensoruntilyoushutitdown.
InsideAtom,we'llmakeafewchangestoourapp.Let'sgetstartedbychanging
Gary to Mike in app.js, and then we'll change the filteredArray variable to var
filteredArray=_.uniq(['Mike']),asshowninthefollowingcode:
console.log('Startingapp.js');
constfs=require('fs');
constos=require('os');
const_=require('lodash');
constnotes=require('./notes.js');
//console.log(_.isString(true));
//console.log(_.isString('Gary'));
varfilteredArray=_.uniq(['Mike']);
console.log(filteredArray);
Now, I'll be saving the file. In the Terminal window, you can see the app
automaticallyrestarted, and within a split second,the new output is shown on
thescreen:
Asshownintheprecedingscreenshot,wenowhaveourarraywithoneitemof
string,Mike.Andthisistherealpowerofnodemon.
Youcancreateyourapplicationsandtheywillautomaticallyrestartoverinthe
Terminal, which is super useful. It'll save you a ton of time and a ton of
headaches.Youwon'thavetoswitchbackandfortheverytimeyoumakeasmall
tweak.This also preventsa ton of errors whereyou are running a web server,
you makea change,and you forget torestart the web server.You might think
yourchangedidn'tworkasexpectedbecausetheappisnotworkingasexpected,
butinreality,youjustneverrestartedtheapp.
Forthemostpart,wewillbeusingnodemonthroughoutthebooksinceit'ssuper
useful. It's only used for development purposes, which is exactly what we're
doingonourlocalmachine.Now,we'llmoveforwardandstartexploringhow
wecangetinputfromtheusertocreateournotesapplication.Thatwillthetopic
ofthenextfewsections.
Beforewegetstarted,weshouldcleanupalotofthecodewe'vealreadywritten
in this section. I'll remove all of the commented-out code in app.js. Then, I'll
simply remove os, where we have fs,os and lodash, since we'll not be using it
throughout the project. I'll also be adding a space between the third-party and
NodemodulesandthefilesI'vewritten,whichareasfollows:
console.log('Startingapp.js');
constfs=require('fs');
const_=require('lodash');
constnotes=require('./notes.js');
Ifindthistobeagoodsyntaxthatmakesitaloteasiertoquicklyscanforeither
third-partyorNodemodules,orthemodulesthatI'vecreatedandrequired.
Nextup,overinnotes.js,we'llremovetheaddfunction;thiswasonlyaddedfor
demonstration purposes, as shown in the following figure. Then we can save
boththenotes.jsandapp.jsfiles,andnodemonwillautomaticallyrestart:
console.log('Startingnotes.js');
module.exports.addNote=()=>{
console.log('addNote');
return'Newnote';
};
module.exports.add=(a,b)=>{
returna+b;
};
Nowwecanremovethegreetings.txtfile.Thatwasusedtodemonstratehowthe
fs module works, and since we already know how it works, we can wipe that
file.Andlastbutnotleast,wecanalwaysshutdownnodemonusingCtrl+C.Now
we'rebackattheregularTerminal.
Andwith thisin place,now weshould moveon, figuringout howwe canget
inputfromtheuser,becausethat'showuserscancreatenotes,removenotes,and
fetchtheirnotes.
Gettinginput
Ifauserwantstoaddanote,weneedtoknowthenote'stitleaswellasthebody
ofthenote.Iftheywanttofetch anote,weneed toknowthetitleof thenote
theywanttofetch,andallthisinformationneedstocomeintoourapp.Andnote
apps,don'treallydoanythingcooluntiltheygetthisdynamicuserinput.Thisis
whatmakesyourscriptsusefulandawesome.
Now, throughout the book, we'll be creating note apps that get input from the
userin a lot of different ways. We'll be using socket I/O to get real-time info
fromawebapp,we'llbecreatingourownAPIsootherwebsitesandserverscan
makeAjaxrequeststo ourapp,butin thissection,we'llstart thingsoffwitha
verybasicexampleofhowtogetuserinput.
We'llbegettinginputfromtheuserinsidethecommandline.Thatmeanswhen
youruntheappinthecommandline,you'llbeabletopassinsomearguments.
TheseargumentswillbeavailableinsideNode,andthenwecandootherthings
withthem,suchascreateanote,deleteanote,orreturnanote.
Gettinginputfromtheuserinside
thecommandline
To start things off, let's run our app from the Terminal. We'll run it pretty
similarlyto how we ran it in the earlier sections: we'll start with node (I'm not
usingnodemonsincewe'llbechangingtheinput),thenwe'lluseapp.js,whichisthe
filewewanttorun,butthenwecanstilltypeothervariables.
We can pass all sorts of command-line arguments in. We could
haveacommand,andthiswouldtelltheappwhattodo,whether
youwanttoaddanote,removeanote,orlistanote.
Ifwewanttoaddanote,thatmightlookasacommandshowninthefollowing
code:
nodeapp.jsadd
Thiscommandwilladdanote;wecanremoveanoteusingtheremovecommand,
asshownhere:
nodeapp.jsremove
Andwecouldlistallofournotesusingthelistcommand:
nodeapp.jslist
Now,whenwerunthiscommand,theappisstillgoingtoworkasexpected.Just
becausewepassedinanewargumentdoesn'tmeanourappisgoingtocrash:
Andweactuallyhaveaccesstothelistargumentalready,we'rejustnotusingit
insidetheapplication.
To access the command-line arguments your app was initialized with, you'll
wanttousethatprocessobjectthatweexploredinthefirstchapter.
Wecanlogoutalloftheargumentsusingconsole.logtoprintthemtothescreen;
it'sontheprocessobject,andthepropertywe'relookingforisargv.
The argv object is short for arguments vector, or in the case of
JavaScript,it'smorelikeanargumentsarray.Thiswillbeanarray
ofallthecommand-lineargumentspassedin,andwecanusethem
tostartcreatingourapplication.
Nowsaveapp.jsandit'lllooklikethefollowing:
console.log('Startingapp.js');
constfs=require('fs');
const_=require('lodash');
constnotes=require('./notes.js');
console.log(process.argv);
Thenwe'llrerunthisfile:
Now,asshownintheprecedingcommandoutput,wehavethreeitemswhichare
asfollows:
ThefirstonepointstotheexecutableforNodethatwasused.
Thesecondonepointstotheappfilethatwasstarted;inthiscase,itwas
app.js.
Thethirdoneiswhereourcommand-lineargumentsstarttocomeintoplay.
Init,wehaveourlistshowingupasastring.
That means we can access that third item in the array, and that will be the
commandforournotesapplication.
Accessing the command-line
argument for the notes
application
Let'saccessthecommand-lineargumentinthearraynow.We'llmakeavariable
calledcommand,andsetitequaltoprocess.argv,andwe'llgrabtheiteminthethird
position(whichislist,asshownintheprecedingcommandoutput),whichisthe
indexoftwoasshownhere:
varcommand=process.argv[2];
Thenwecanlogthatouttothescreenbyloggingoutcommandthestring.Then,as
thesecondargument,I'llpassintheactualcommandthatwasused:
console.log('Command:',command);
Andthis isjusta simplelogto keeptrackof howtheapp isgettingexecuted.
The cool stuff is going to come when we add if statements that do different
thingsdependingonthatcommand.
Addingif/elsestatements
Let'screateanif/elseblockbelowtheconsole.log('Command:',command);.We'lladd
if(command==='add'),asshownhere:
console.log('Startingapp.js');
constfs=require('fs');
const_=require('lodash');
constnotes=require('./notes.js');
varcommand=process.argv[2];
console.log('Command:',command);
if(command==='add')
In this case, we'll go through the process of addinga new note. Now, we're not
specifyingtheotherargumentshere,suchasthetitleorthebody(we'lldiscuss
thatinlatersections).Fornow,ifthecommanddoesequaladd,we'lluseconsole.log
toprintAddingnewnote,asshowninthefollowingcode:
console.log('Startingapp.js');
constfs=require('fs');
const_=require('lodash');
constnotes=require('./notes.js');
varcommand=process.argv[2];
console.log('Command:',command);
if(command==='add'){
console.log('Addingnewnote');
}
Andwecandotheexactsamethingwithacommandsuchaslist.We'lladdelse
if(command==='list'),asshownhere:
console.log('Startingapp.js');
constfs=require('fs');
const_=require('lodash');
constnotes=require('./notes.js');
varcommand=process.argv[2];
console.log('Command:',command);
if(command==='add'){
console.log('Addingnewnote');
}elseif(command==='list')
Ifthecommanddoesequalthestringlist,we'llrunthefollowingblockofcode
usingconsole.logtoprintListingallnotes.Wecanalsoaddanelseclauseifthere
isnocommand,whichisconsole.log('Commandnotrecognized'),asshownhere:
console.log('Startingapp.js');
constfs=require('fs');
const_=require('lodash');
constnotes=require('./notes.js');
varcommand=process.argv[2];
console.log('Command:',command);
if(command==='add'){
console.log('Addingnewnote');
}elseif(command==='list'){
console.log('Listingallnotes');
}else{
console.log('Commandnotrecognized');
}
With this in place, we can now rerun our app for a third time, and this time
around,you'llseewehavethecommandequaltolist,andlistingallnotesshows
up,asshowninthefollowingcode:
if(command==='add'){
console.log('Addingnewnote');
}elseif(command==='list'){
console.log('Listingallnotes');
}else{
console.log('Commandnotrecognized');
}
Thismeanswewereabletouseourargumenttorundifferentcode.Noticethat
wedidn't run Adding new note and we didn'trun Command not recognized. We could,
however,switchthenodeapp.jscommandfromlisttoadd,andinthatcase,we'll
getAddingnewnoteprinting,asshowninthefollowingscreenshot:
Andifwerunacommandthatdoesn'texist,forexampleread,youcanseeCommand
notrecognizedprintsasshowninthefollowingscreenshot:
Exercise – adding two else if
clausestoanifblock
Now, what I'd like you to do is add two more else if clauses to our if block,
whichwillbeasfollows:
Onewillbeforthereadcommand,whichwillberesponsibleforgettingan
individualnoteback
Anotheronecalledremovewillberesponsibleforremovingthenote
Allyouhavetodoisaddtheelseifstatementforbothofthem,andthenjustput
aquickconsole.logprintingsomethinglikeFetchingnoteorRemovingnote.
Takea momentto knockthat outas yourchallengeforthis section.Once you
addthosetwoelseifclauses,runbothofthemfromtheTerminalandmakesure
yourlogshowsup.Ifitdoesshowup,youaredone,youcanmoveaheadwith
thissection.
Solutiontotheexercise
Forthesolution,thefirstthingI'lldoistoaddanelseifforread. I'llopen and
close my curly braces and hit enter right in the middle so everything gets
formattedcorrectly.
Intheelseifstatement,I'llcheck whetherthecommandvariable equalsthestring
read,asshownhere:
console.log('Startingapp.js');
constfs=require('fs');
const_=require('lodash');
constnotes=require('./notes.js');
varcommand=process.argv[2];
console.log('Command:',command);
if(command==='add'){
console.log('Addingnewnote');
}elseif(command==='list'){
console.log('Listingallnotes');
}elseif(){
}else{
console.log('Commandnotrecognized');
}
In the future, we'll be calling methods that update our local
databasewiththenotes.
Fornow,we'lluseconsole.logtoprintReadingnote:
console.log('Startingapp.js');
constfs=require('fs');
const_=require('lodash');
constnotes=require('./notes.js');
varcommand=process.argv[2];
console.log('Command:',command);
if(command==='add'){
console.log('Addingnewnote');
}elseif(command==='list'){
console.log('Listingallnotes');
}elseif(command==='read'){
}else{
console.log('Commandnotrecognized');
}
Thenextthingyouneedtodoisaddanelseif clausethatchecks whetherthe
commandequalsremove.Intheelseif,I'llopenandclosemyconditionandhitenter
justasIdidinthepreviouselseifclause;thistime,I'lladdifthecommandequals
remove, we want to remove the note. And in that case, all we'll do is to use
console.logtoprintReadingnote,asshowninthefollowingcode:
console.log('Startingapp.js');
constfs=require('fs');
const_=require('lodash');
constnotes=require('./notes.js');
varcommand=process.argv[2];
console.log('Command:',command);
if(command==='add'){
console.log('Addingnewnote');
}elseif(command==='list'){
console.log('Listingallnotes');
}elseif(command==='read'){
console.log('Readingnote');
}else{
console.log('Commandnotrecognized');
}
Andwiththisinplace,wearedone.Ifwerefertothecodeblock,we'veadded
twonewcommandswecanrunoverintheTerminal,andwecantestthose:
if(command==='add'){
console.log('Addingnewnote');
}elseif(command==='list'){
console.log('Listingallnotes');
}elseif(command==='read'){
console.log('Readingnote');
}else{
console.log('Commandnotrecognized');
}
Firstup,I'llrunnodeapp.jswiththereadcommand,andReadingnoteshowsup:
console.log('Startingapp.js');
constfs=require('fs');
const_=require('lodash');
constnotes=require('./notes.js');
varcommand=process.argv[2];
console.log('Command:',command);
if(command==='add'){
console.log('Addingnewnote');
}elseif(command==='list'){
console.log('Listingallnotes');
}elseif(command==='read'){
console.log('Readingnote');
}elseif(command=='remove'){
console.log('Removingnote');
}else{
console.log('Commandnotrecognized');
}
ThenI'llrerunthecommand;thistime,I'llbeusingremove.AndwhenIdothat,
Removingnoteprintstothescreen,asshowninthisscreenshot:
I'llwrapupmytestingusingacommandthatdoesn'texist,andwhenIrunthat,
youcanseeCommandnotrecognizedshowsup.
Getting the specific note
information
Now,whatwedidintheprevioussubsectionisstep1.Wenowhavesupportfor
variouscommands.Thenextthingweneedtofigureoutishowwe'llgetmore
specificinformation.Forexample, whichnotedo youwanttoremove?Which
notedoyouwanttoread?Andwhatdoyouwantthenotetexttobeinthecase
ofaddinganote?ThisisallinformationweneedtogetfromtheTerminal.
Now,gettingitisgoingtobeprettysimilartowhatwedidearlier,andtoshow
you what it looks like, we'll print the entire argv object once again, using the
followingcommand:
console.log(process.argv);
OverintheTerminal,wecannowrunamorecomplexcommand.Let'ssaywe
wanttoremoveanoteusingthenodeapp.jsremovecommand,andwe'lldothatby
itstitle.Wemightusethetitleargument,whichlookslikethefollowingcode:
nodeapp.jsremove--title
In this title argument, we have -- (two) hyphens followed by the argument
name,whichistitle,followedbythe=(equals)sign.Thenwecantypeournote
title. Maybe the note title is secrets. This will pass the title argument into our
application.
Now,thereareacoupleofdifferentwaysyoucouldformatthetitleargument,
whichareasfollows:
Youcouldhavethetitlesecretsliketheoneintheprecedingcommand
You could have title equals secrets inside quotes, which will let us use
spacesinthetitle:
nodeapp.jsremove--title=secrets
Youcanremovethe=(equals)signaltogetherandsimplyputaspace:
nodeapp.jsremove--title="secrets2"
Nomatterhowyouchoosetoformatyourargument,theseareallvalidwaysto
passinthetitle.
Asyousee inthepreceding screenshot,Iamusingdoublequotes
when wrapping my string. Now, if you switch to single quotes, it
willnotbreakonLinuxorOSX,butitwillbreakonWindows.That
meanswhenyou'repassingincommand-lineargumentssuchasthe
titleorthe notebody, you'llwanttowrap yourstrings,when you
have spaces, in double quotes, not single. So, if you are using
Windowsandyou'regettingsomesortofunexpectedbehaviorwith
your arguments, make sure you're using double quotes instead of
single;thatshouldfixtheissue.
For the moment, I'll keep the = (equals) sign and the quotes and rerun the
command:
nodeapp.jsremove--title="secrets2"
WhenIrunthecommand,youcanseeinthefollowingcodeoutputthatwehave
ourtwoarguments:
Thesearetheargumentsthatwedon'tneed,thenwehaveourremovecommand,
which is the third one, and we now have a new fourth string, the title that is
equal to secrets 2. And our argument was successfully passed into the
application.Theproblemisthatit'snotveryeasytouse.Inthefourthstring,we
havetoparseoutthekey,whichistitle,andthevalue,whichissecrets2.
When we used the command, which was the third argument in the previous
section,itwasaloteasiertouseinsideourapp.Wesimplypulleditoutofthe
arguments array and we referenced it by using the command variable and
checkingwhetheritequaledadd,list,read,orremove.
Things get a lot more complex as we use different styles for passing in the
arguments.Ifwe rerunthelastcommand withaspaceinstead ofan=(equals)
sign, as shown in the following code, which is perfectly valid, our arguments
arraynowlookscompletelydifferent:
In the preceding code output, you can see that we have the title as the fourth
item,and we have the value, which is secrets 2, as the fifth, which means we
have to add other conditions for parsing. And this turns into a pain really
quickly,whichiswhywewillnotdoit.
We'lluseathird-partymodulecalledyargsinthenextchaptertomakeparsing
thecommand-line arguments effortless. Instead of having strings, as shown in
this one or the one we discussed earlier, we'll get an object where the title
propertyequalsthesecrets2string.Thatwillmakeitsupereasytoimplementthe
restofthenotesapplication.
Now,parsingcertaintypesofcommand-linearguments,suchaskeyvaluepairs,
becomesa lotmorecomplex, whichis why, inthe nextchapter,we'll beusing
yargstodojustthat.
Summary
Inthischapter,welearnedhowtouserequiretoloadinmodulesthatcomewith
Node.js.Wecreatedourfilesforournotesapplicationandrequiredtheminside
app.js. We explored how to use built-in modules and we explored how to use
modules wedefined. We found out how to require other files that we created,
andhowtoexportthingssuchaspropertiesandfunctionsfromthosefiles.
Weexplorednpmalittlebit,howwecanuse npm initto generatea package.json
file,andhowwecaninstallandusethird-partymodules.Next,weexploredthe
nodemonmodule,usingittoautomaticallyrestartourappaswemakechangestoa
file.Last,welearnedhowtogetinputfromtheuser,whichisneededtocreate
the notesapplication. We learned that we can use command-line arguments to
passdataintoourapp.
In the next chapter, we'll explore some more interesting Node fundamental
concepts,includingyargs,JSON,andRefactor.
NodeFundamentals–Part2
Inthischapter,we'llcontinueourdiscussiononsomemorenodefundamentals.
We'llexploreyargs,andwe'llseehowtoparsecommand-lineargumentsusing
process.argvandyargs.Afterthat,we'llexploreJSON.JSONisnothingmorethan
astringthatlookskindoflikeaJavaScriptobject,withthenotabledifferences
beingthatitusesdoublequotesinsteadofsinglequotesandallofyourproperty
names—likenameandage, in thiscase—require quotes around them. We'll look
into how to convert an object into a string, then define that string, use it, and
convertitbacktoanobject.
Afterwe'vedonethat,we'llfillouttheaddNotefunction.Finally, we'lllook into
refactor, moving the functionality into individual functions and testing the
functionality.
Morespecifically,we'llgothroughfollowingtopics:
yargs
JSON
Addingnote
Refactor
yargs
Inthissection,wewilluseyargs,athird-partynpmmodule,tomaketheprocess
of parsing much easier. It will let us access things such as title and body
informationwithoutneedingtowriteamanualparser.Thisisagreatexampleof
whenyoushouldlookforannpmmodule.Ifwedon'tuseamodule,itwouldbe
more productivefor our Node applicationto use athird-party module that has
beentestedandthoroughlyvetted.
Togetstarted,we'llinstallthemodule,thenwe'lladditintotheproject,parsing
forthingssuchasatitleofthebody,andwe'llcallallthefunctionsthatwillget
definedoverinnotes.js.Ifthecommandisadd,we'llcalladdnote,soon.
Installingyargs
Now,let'sviewthedocumentspageforyargs.It'salwaysagoodideatoknow
whatyou'regettingyourselfinto.IfyousearchforyargsonGoogle,youshould
find the GitHub page as your first search result. As shown in the following
screenshot,wehavetheGitHubpagefortheyargslibrary:
Now,yargsisaverycomplexlibrary.Ithasatonoffeaturesforvalidatingall
sortsofinput,andithasdifferentwaysinwhichyoucanformatthatinput.We
will start with a very basic example, although we will be introducing more
complexexamplesthroughoutthischapter.
Ifyouwanttolookatanyotherfeaturesthatwedon'tdiscussinthe
chapter,oryoujustwanttoseehowsomethingworksthatwehave
talkedabout,youcanalwaysfinditintheyargdocuments.
We'llnowmoveintoTerminaltoinstallthismoduleinsideofourapplication.To
dothis,we'llusenpminstallfollowedbythemodulename,yargs,andinthiscase,
I'll use the @ sign to specify the specific version of the module I want to use,
11.0.0,whichisthemostrecentversionatthetimeofwriting.Next,I'lladdthe
saveflag,which,asweknow,updatesthepackage.jsonfile:
npminstallyargs@11.0.0--save
If I leave off the save flag, yargs will get installed into the
node_modules folder, but if we wipe that node_modules folder later and
runnpminstall,yargswon'tgetreinstalledbecauseit'snotlistedin
thepackage.jsonfile.Thisiswhyweusethesaveflag.
Runningyargs
Nowthat we'veinstalled yargs,wecan moveover into Atom,inside ofapp.js,
andgetstartedwithusingit.Thebasicsofyargs,theverycoreofitsfeatureset,
isreallysimpletotakeadvantageof.Thefirstthingwe'lldoistorequireitup,as
wedidwithfsandlodashinthepreviouschapter.Let'smakeaconstantandcallit
yargs,settingitequaltorequire('yargs'),asshownhere:
console.log('Startingapp.js');
constfs=require('fs');
const_=require('lodash');
constyargs=require('yargs');
constnotes=require('./notes.js');
varcommand=process.argv[2];
console.log('Command:',command);
console.log(process.argv);
if(command==='add'){
console.log('Addingnewnote');
}elseif(command==='list'){
console.log('Listingallnotes');
}elseif(command==='read'){
console.log('Readingnote');
}elseif(command==='remove'){
console.log('Removingnote');
}else{
console.log('Commandnotrecognized');
}
From here, we can fetch the arguments as yargs parses them. It will take the
same process.argv array that we discussed in the previous chapter, but it goes
behind the scenes and parses it, giving us something that's much more useful
than what Node gives us. Just above the command variable, we can make a const
variablecalledargv,settingitequaltoyargs.argv,asshownhere:
console.log('Startingapp.js');
constfs=require('fs');
const_=require('lodash');
constyargs=require('yargs');
constnotes=require('./notes.js');
constargv=yargs.argv;
varcommand=process.argv[2];
console.log('Command:',command);
console.log(process.argv);
if(command==='add'){
console.log('Addingnewnote');
}elseif(command==='list'){
console.log('Listingallnotes');
}elseif(command==='read'){
console.log('Readingnote');
}elseif(command==='remove'){
console.log('Removingnote');
}else{
console.log('Commandnotrecognized');
}
The yargs.argv module is where the yargs library stores its version of the
argumentsthatyourappranwith.Nowwecanprintitusingconsole.log,andthis
will let us take a look at the process.argv and yargs.argv variables; we can also
compare them and see how yargs differs. For the command where we use
console.logtoprintprocess.argv,I'llmakethefirstargumentastringcalledProcess
sothatwecandifferentiateitinTerminal.We'llcallconsole.logagain.Thefirst
argument will be the Yargs string, and the second one will be the actual argv
variable,whichcomesfromyargs:
console.log('Startingapp.js');
constfs=require('fs');
const_=require('lodash');
constyargs=require('yargs');
constnotes=require('./notes.js');
constargv=yargs.argv;
varcommand=process.argv[2];
console.log('Command:',command);
console.log('Process',process.argv);
console.log('Yargs',argv);
if(command==='add'){
console.log('Addingnewnote');
}elseif(command==='list'){
console.log('Listingallnotes');
}elseif(command==='read'){
console.log('Readingnote');
}elseif(command==='remove'){
console.log('Removingnote');
}else{
console.log('Commandnotrecognized');
}
Nowwecanrunourapp(refertotheprecedingcodeblock)afewdifferentways
andseehowthesetwoconsole.logstatementsdiffer.
Firstup,we'llrunatnodeapp.jswiththeaddcommand,andwecanrunthisvery
basicexample:
nodeapp.jsadd
Wealreadyknowwhattheprocess.argvarraylookslikefromthepreviouschapter.
Theusefulinformationisthethirdstringinsideofthearray,whichis'add'.Inthe
fourthstring,Yargsgivesusanobjectthatlooksverydifferent:
Asshown inthe precedingcodeoutput, firstwe havethe underscoreproperty,
thencommandssuchasaddarestored.
IfIweretoaddanothercommand,sayadd,andthenIweretoaddamodifier,say
encrypted,youwouldseethataddwouldbethefirstargumentandencryptedthe
second,asshownhere:
nodeapp.jsaddencrypted
Sofar,yargsreallyisn'tshining.Thisisn'tmuchmoreusefulthanwhatwehave
inthepreviousexample.Whereitreallyshinesiswhenwestartpassinginkey-
value pairs, such as the title example we used in the Getting input section of
NodeFundamentals-Part1inchapter2.Icansetmytitleflagequalto secrets,
pressenter,andthistimearound,wegetsomethingmuchmoreuseful:
nodeapp.jsadd--title=secrets
In the following code output, we have the third string that we would need to
parseinordertofetchthevalueandthekey,andinthefourthstring,weactually
haveatitlepropertywithavalueofsecrets:
Also,yargshasbuilt-inparsingforallthedifferentwaysyoucouldspecifythis.
Wecaninsertaspaceaftertitle,anditwillstillworkjustasitdidbefore;we
canaddquotesaroundsecrets,oraddotherwords,likesecretsfromAndrew,andit
will still parses it correctly, setting the title property to the secrets from Andrew
string,asshownhere:
nodeapp.jsadd--title"secretsfromAndrew"
This is where yargs really shines! It makes the process of parsing your
argumentsaloteasier.Thismeansthatinsideourapp,wecantakeadvantageof
thatparsingandcalltheproperfunctions.
Workingwiththeaddcommand
Let'sworkwiththeaddcommand,forexample,forparsingyourargumentsand
calling the functions. Once the add command gets called, we want to call a
functiondefinedinnotes,whichwillberesponsibleforactuallyaddingthenote.
Thenotes.addNotefunctionwillgetthejobdone.Now,whatdowewanttopassto
theaddNotefunction?Wewanttopassintwothings:thetitle,whichisaccessible
onargv.title,aswesawintheprecedingexample;andthebody,argv.body:
console.log('Startingapp.js');
constfs=require('fs');
const_=require('lodash');
constyargs=require('yargs');
constnotes=require('./notes.js');
constargv=yargs.argv;
varcommand=process.argv[2];
console.log('Command:',command);
console.log('Process',process.argv);
console.log('Yargs',argv);
if(command==='add'){
console.log('Addingnewnote');
notes.addNote(argv.title,argv.body);
}elseif(command==='list'){
console.log('Listingallnotes');
}elseif(command==='read'){
console.log('Readingnote');
}elseif(command==='remove'){
console.log('Removingnote');
}else{
console.log('Commandnotrecognized');
}
Currently, these command-line arguments, title and body, aren't
required.Sotechnically,theusercouldruntheapplicationwithout
oneofthem,whichwouldcauseittocrash,butinfuture,we'llbe
requiringbothofthese.
Nowthatwehavenotes.addNoteinplace,wecanremoveourconsole.logstatement,
which was just a placeholder, and we can move into the notes application
notes.js.
Insidenotes.js,we'llgetstartedbymakingavariablewiththesamenameasthe
methodweusedoverapp.jsandaddNote,andwewillsetitequaltoananonymous
arrowfunction,asshownhere:
varaddNote=()=>{
};
Now,thisaloneisn'ttoouseful,becausewe'renotexportingtheaddNotefunction.
Below the variable, we can define module.exports in a slightly different way. In
previous sections, we added properties onto exports to export them. We can
actuallydefineanentireobjectthatgetssettoexports,andinthiscase,wecanset
addNoteequaltotheaddNotefunctiondefinedinprecedingcodeblock:
module.exports={
addNote:addNote
};
InES6,there'sactuallyashortcutforthis.Whenyou'resettingan
object attribute and a value that's a variable and they're both
exactly the same, you can actually leave off the colon and the
value.Eitherway,theresultidentical.
In the preceding code, we're setting an object equal to module.exports, and that
objecthasaproperty,addNote,whichpointstotheaddNotefunctionwedefinedasa
variableintheprecedingcodeblock.
Onceagain,addNote:andaddNoteareidenticalinsideofES6.Wewillbeusingthe
ES6syntaxforeverythingthroughoutthisbook.
NowIcantakemytwoarguments,titleandbody,andactuallydosomethingwith
them. In this case, we'll call console.log and Adding note, passing in the two
arguments as the second and third argument to console.log, title and body, as
shownhere:
varaddNote=(title,body)=>{
console.log('Addingnote',title,body);
};
Nowwe'reinaprettygoodpositiontoruntheaddcommandwithtitleand body
and see if we get exactly what we'd expect, which is the console.log statement
shownintheprecedingcodetoprint.
Over in Terminal, we can start by running the app with node app.js, and then
specifythefilename.We'llusetheaddcommand;whichwillruntheappropriate
function.Then,we'llpassintitle,settingitequaltosecret,andthenwecanpass
inbody,whichwillbeoursecondcommand-lineargument,settingthatequalto
thestring,Thisismysecret:
nodeapp.jsadd--title=secret--body="Thisismysecret"
Inthiscommand,wespecifiedthreethings:theaddcommandthetitleargument,
which gets set to secret; and the body argument, which gets set to "This is my
secret".Ifallgoeswell,we'llgettheappropriatelog.Let'srunthecommand.
Inthefollowingcommandoutput,youcanseeAddingnotesecret,whichisthe
title;andThisismysecret,whichisthebody:
Withthisinplace,wenowhaveoneofourmethodssetupandreadytogo.The
nextthingthatwe'lldoisconverttheothercommandswehave—thelist,read,
andremovecommands.Let'slookintoonemorecommand,andthenyou'lldothe
othertwobyyourselfasexercises.
Workingwiththelistcommand
Now, with the list command, I'll remove the console.log statement and call
notes.getAll,asshownhere:
console.log('Startingapp.js');
constfs=require('fs');
const_=require('lodash');
constyargs=require('yargs');
constnotes=require('./notes.js');
constargv=yargs.argv;
varcommand=process.argv[2];
console.log('Command:',command);
console.log('Process',process.argv);
console.log('Yargs',argv);
if(command==='add'){
notes.addNote(argv.title,argv.body);
}elseif(command==='list'){
notes.getAll();
}elseif(command==='read'){
console.log('Readingnote');
}elseif(command==='remove'){
console.log('Removingnote');
}else{
console.log('Commandnotrecognized');
}
Atsomepoint,notes.getAllwillreturn allof thenotes. Now, getAlldoesn't take
anyargumentssinceitwillreturnallofthenotesregardlessofthetitle.Theread
commandwillrequireatitle,andremovewillalsorequirethetitleofthenoteyou
wanttoremove.
Fornow,wecancreatethegetAllfunction.Insidenotes.js,we'llgothroughthat
process again. We'll start by making a variable, calling it getAll, and setting it
equal to an arrow function, which we've used before. We start with our
argumentslist, then we set up the arrow (=>), which is the equal sign and the
greater than sign. Next, we specify the statements we want to run. Inside our
codeblock,we'llrunconsole.log(Gettingallnotes),asshownhere:
vargetAll=()=>{
console.log('Gettingallnotes');
};
ThelaststeptotheprocessafteraddingthatsemicolonwillbetoaddgetAllto
theexports,asshowninthefollowingcodeblock:
module.exports={
addNote,
getAll
};
Remember that in ES6, if you have a property whose name is
identicaltothevalue,whichisavariable,youcansimplyremove
thevaluevariableandthecolon.
NowthatwehavegetAllinnotes.jsinplace,andwe'vewireditupinapp.js,we
canrunthingsoverinTerminal.Inthiscase,we'llrunthelistcommand:
nodeapp.jslist
In the preceding code output, you can see at the bottom that Getting all notes
prints to the screen. Now that we have this in place, we can remove
console.log('Process',process.argv)fromthecommandvariableinapp.js.Theresultant
codewilllooklikethefollowingcodeblock:
console.log('Startingapp.js');
constfs=require('fs');
const_=require('lodash');
constyargs=require('yargs');
constnotes=require('./notes.js');
constargv=yargs.argv;
varcommand=process.argv[2];
console.log('Command:',command);
console.log('Yargs',argv);
if(command==='add'){
notes.addNote(argv.title,argv.body);
}elseif(command==='list'){
notes.getAll();
}elseif(command==='read'){
console.log('Readingnote');
}elseif(command==='remove'){
console.log('Removingnote');
}else{
console.log('Commandnotrecognized');
}
Wewillkeeptheyargslogaroundsincewe'llbeexploringtheotherwaysand
methodstouseyargsthroughoutthechapter.
Now that we have the list command in place, next, I'd like you to create a
methodforthereadandremovecommands.
Thereadcommand
Whentheread commandis used,we want tocall notes.getNote,passingintitle.
Now,titlewillgetpassedinandparsedusingyargs,whichmeansthatwecan
useargv.titletofetchit.Andthat'sallwehavetodowhenitcomestocallingthe
function:
console.log('Startingapp.js');
constfs=require('fs');
const_=require('lodash');
constyargs=require('yargs');
constnotes=require('./notes.js');
constargv=yargs.argv;
varcommand=process.argv[2];
console.log('Command:',command);
console.log('Yargs',argv);
if(command==='add'){
notes.addNote(argv.title,argv.body);
}elseif(command==='list'){
notes.getAll();
}elseif(command==='read'){
notes.getNote(argv.title);
}elseif(command==='remove'){
console.log('Removingnote');
}else{
console.log('Commandnotrecognized');
}
The next step is to define getNote, because currently it doesn't exist. Over in
notes.js, right below the getAll variable, we can make a variable called getNote,
which will be a function. We'll use the arrow function, and it will take an
argument;it will take the note title. The getNote function takes the title, then it
returnsthebodyforthatnote:
vargetNote=(title)=>{
};
InsidegetNote,wecanuseconsole.logtoprintsomethinglikeGettingnote,followed
by the title of the note you will fetch, which will be the second argument to
console.log:
vargetNote=(title)=>{
console.log('Gettingnote',title);
};
Thisisthefirstcommand,andwecannowtestitbeforewegoontothesecond
one,whichisremove.
OverinTerminal,wecanusenodeapp.jstorunthefile.We'llbeusingthenew
readcommand,passinginatitleflag.I'lluseadifferentsyntax,wheretitlegets
setequaltothevalueoutsideofquotes.I'llusesomethinglikeaccounts:
nodeapp.jsread--titleaccounts
Thisaccountsvaluewillreadtheaccountsnoteinthefuture,anditwillprintitto
thescreen,asshownhere:
Asyoucanseeintheprecedingcodeoutput,wegetanerror,whichwe'lldebug
now.
Dealingwiththeerrorsinparsing
commands
Gettinganerrorisnottheendoftheworld.Gettinganerrorusuallymeansthat
youhaveasmalltypooryouforgotonestepintheprocess.So,we'llfirstfigure
outhowtoparsethroughtheseerrormessages,becausetheerrormessagesyou
getinthecodeoutputcanbeprettydaunting.Let'srefertothecodeoutputerror
here:
Asyoucansee,thefirstlineshowsyouwheretheerroroccurred.It'sinsideof
ourapp.jsfile,andthenumber19afterthecolonisthelinenumber.Itshowsyou
exactlywherethingswentbad.TheTypeError:notes.getNoteisnotafunctionlineis
tellingyouprettyclearlythatthegetNotefunction youtriedtorundoesn'texist.
Nowwecantakethisinformationanddebugourapp.
Inapp.js,weseethatwecallnotes.getNote.Everythinglooksgreat,butwhenwe
moveintonotes.js,werealizethatweneveractuallyexportedgetNote.Thisiswhy
whenwetrytocallthefunction,wegetgetNoteisnot afunction.Allwehaveto
dotofixthaterrormessageisexportgetNote,asshownhere:
module.exports={
addNote,
getAll,
getNote
};
NowwhenwesavethefileandreruntheappfromTerminal,we'llgetwhatwe
expect—Gettingnotefollowedbythetitle,whichisaccounts,asshownhere:
This is how we can debug our error messages. Error messages contain really
useful information. For the most part, the first couple of lines are code that
you'vewritten,andtheotheronesareinternalNodecodeorthird-partymodules.
In our case, the first line of the stack trace is important, as it shows exactly
wheretheerroroccurred.
Theremovecommand
Now,sincethereadcommandisworking,wecanmoveontothelastone,which
istheremovecommand.Here,I'llcallnotes.removeNote,passinginthetitle,whichas
weknowisavailableinargv.title:
console.log('Startingapp.js');
constfs=require('fs');
const_=require('lodash');
constyargs=require('yargs');
constnotes=require('./notes.js');
constargv=yargs.argv;
varcommand=process.argv[2];
console.log('Command:',command);
console.log('Yargs',argv);
if(command==='add'){
notes.addNote(argv.title,argv.body);
}elseif(command==='list'){
notes.getAll();
}elseif(command==='read'){
notes.getNote(argv.title);
}elseif(command==='remove'){
notes.removeNote(argv.title);
}else{
console.log('Commandnotrecognized');
}
Next up, we'll define the removeNote function over inside of our notes API file,
rightbelowthegetNotevariable:
varremoveNote=(title)=>{
console.log('Removingnote',title);
};
Now,removeNotewillworkmuchthesamewayasgetNote.Allitneedsisthetitle;it
canusethisinformationtofindthenoteandremoveitfromthedatabase.This
willbeanarrowfunctionthattakesthetitleargument.
Inthiscase,we'llprinttheconsole.logstatement,Removingnote;then,asthesecond
argument,we'llsimplyprinttitlebacktothescreentomakesurethatit'sgoing
through the process successfully. This time around, we'll export our removeNote
function;we'lldefineitusingtheES6syntax:
module.exports={
addNote,
getAll,
getNote,
removeNote
};
The last thing to do is test it and make sure it works. We can reload the last
commandusing the up arrow key. We change read to remove, and that is all we
needtodo.We'restillpassinginthetitleargument,whichisgreat,becausethat
iswhatremoveneeds:
nodeapp.jsremove--titleaccounts
When I run this command, we get exactly what we expected. Removing note
printstothescreen,asshowninthefollowingcodeoutput,andthenwegetthe
titleofthenotethatwe'resupposedtoberemoving,whichisaccounts:
Thislooksgreat!Thatisallittakestouseyargstoparseyourarguments.
With this, we now have a place to define all of that functionality, for saving,
reading,listing,andremovingnotes.
Fetchingcommand
ThelastthingIwanttodiscussbeforewewrapupthissectionis—howwefetch
command.
Asweknow,commandisavailableinthe_propertyasthefirstandonlyitem.This
means that in the app.js,var command statement, we can set command equal to argv,
then._,andthenwe'lluse[]tograbthefirstiteminthearray,asshowninthe
followingcode:
console.log('Startingapp.js');
constfs=require('fs');
const_=require('lodash');
constyargs=require('yargs');
constnotes=require('./notes.js');
constargv=yargs.argv;
varcommand=argv._[0];
console.log('Command:',command);
console.log('Yargs',argv);
if(command==='add'){
notes.addNote(argv.title,argv.body);
}elseif(command==='list'){
notes.getAll();
}elseif(command==='read'){
notes.getNote(argv.title);
}elseif(command==='remove'){
notes.removeNote(argv.title);
}else{
console.log('Commandnotrecognized');
}
With this in place, we now have the same functionality, but we'll use yargs
everywhere.IfIrerunthelastcommand,wecantestthatthefunctionalitystill
works.Anditdoes!Asshowninthefollowingcommandoutput,wecanseethat
Command:removeshowsup:
Next,we'lllookintofillingouttheindividualfunctions.We'lltakealookfirstat
howwecanuseJSONtostoreournotesinsideourfilesystem.
JSON
Nowthatyouknowhowtoparsecommand-lineargumentsusingprocess.argvand
yargs,you'vesolvedthefirstpiecetothepuzzleforthenotesapplication.Now,
howdowegetthatuniqueinputfromtheuser?Thesecondpiecetothepuzzleis
tosolvehowwestorethisinformation.
Whensomeoneaddsanewnote,wewanttosaveitsomewhere,preferablyon
thefilesystem.Sothenexttimetheytrytofetch,remove,orreadthatnote,they
actuallygetthenoteback.Todothis,we'llneedtointroducesomethingcalled
JSON. If you're already familiar with JSON, you probably know it is super
popular. It standsfor JavaScriptObjectNotation, andit's a way torepresent
JavaScriptarraysandobjectsusingastring.Now,whywouldyoueverwantto
dothat?
Well, you might want to do that because strings are just text, and that's pretty
muchsupportedanywhere.IcansaveJSONtoatextfile,andthenIcanreadit
later,parseit backintoa JavaScriptarray orobject,and dosomething withit.
Thisisexactlywhatwe'lltakealookatinthissection.
ToexploreJSONandhowitworks,let'sgoaheadandmakeanewfolderinside
ourprojectcalledplayground.
Throughout the book, I'll create the playground foldersand various
projects, which store simple one-off files that aren't a part of the
biggerapplication;they'rejustawayto explorea newfeature or
learnanewconcept.
In the playground folder, we'll make a file called json.js, this is where we can
explorehowJSONworks.Togetstarted,let'smakeaverysimpleobject.
Convertingobjectsintostrings
Let'sfirstmakeavariablecalledobj,settingitequaltoanobject.Onthisobject,
we'lljustdefineoneproperty,name,andsetitequaltoyourfirstname;I'llsetthis
oneequaltoAndrew,asshownhere:
varobj={
name:'Andrew'
};
Now,let'sassumethatwewanttotakethisobjectandworkonit.Let'ssaywe
wantto,forexample,senditbetweenserversasastringandsaveittoatextfile.
Todothis,we'llneedtocalloneJSONmethod.
Let'stakeamomenttodefineavariabletostoretheresult,stringObj,andwe'llset
itequaltoJSON.stringify,asshownhere:
varstringObj=JSON.stringify(obj);
The JSON.stringify method takes your object, in this case, the obj variable, and
returnstheJSON-stringifiedversion.ThismeansthattheresultstoredinstringObj
isactuallyastring.It'snolongeranobject,andwecantakealookatthatusing
console.log.I'lluseconsole.logtwice.Firstup,we'llusethetypeofoperatortoprint
thetypeofthestringobjecttomakesurethatitactuallyisastring.Sincetypeofis
anoperator,itgetstypedinlowercase,thereisnocamelcasing.Then,youpass
inthevariablewhosetypeyouwanttocheck.Nextup,wecanuseconsole.logto
printthecontentsofthestringitself,printingoutthestringObjvariable,asshown
here:
console.log(typeofstringObj);
console.log(stringObj);
Whatwe'vedonehereiswe'vetakenanobject,converteditintoaJSONstring,
andprinteditontothescreen.OverinTerminal,I'llnavigateintotheplayground
folderusingthefollowingcommand:
cdplayground
For now, it doesn't matter where you run the command, but in
futureitwillmatterwhenweareintheplaygroundfolder,sotakea
momenttonavigateintoit.
Wecannow usenodeto runour json.jsfile. Whenwe run thefile, wesee two
things:
Asshown in thepreceding code output,first, wewill get ourtype, whichis a
string,andthisisgreat,becauseremember,JSONisastring.Next,wewillget
ourobject,whichlooksprettysimilartoaJavaScriptobject,butthereareafew
differences.Thesedifferencesareasfollows:
Firstup,yourJSONwillhaveitsattributenamesautomaticallywrappedin
doublequotes.ThisisarequirementoftheJSONsyntax.
Next up, you'll notice your strings are also wrapped in double quotes as
opposedtosinglequotes.
Now,JSONdoesn'tjustsupportstringvalues,youcanuseanarray,aBoolean,a
number,or anything else. All of those types are perfectly valid inside of your
JSON. In this case, we have a very simple example where we have a name
propertyandit'ssetto"Andrew".
Thisistheprocessoftakinganobjectandconvertingitintoastring.Nextup,
we'lldefineastringand convertthatintoan objectwecanactually useinour
app.
Definingastringandusinginapp
asanobject
Let'sgetstartedbymakingavariablecalledpersonString,andwe'lltosetitequal
toastringusingsinglequotessinceJSONusesdoublequotesinsideofitself,as
shownhere:
varpersonString='';
Then we'll define our JSON in the quotes. We'll start by opening and closing
somecurly braces. We'll use double quotes to create our first attribute, which
we'llcallname,andwe'llsetthatattributeequaltoAndrew.Thismeansthatafterthe
closing quote, we'll add :; then we'll open and close double quotes again and
typethevalueAndrew,asshownhere:
varpersonString='{"name":"Andrew"}';
Nextup,wecanaddanotherproperty.Afterthevalue,Andrew,I'llcreateanother
propertyafterthecomma,calledage,whichwillbesetequaltoanumber.Ican
usemycolonandthendefinethenumberwithoutthequotes,inthiscase,25:
varpersonString='{"name":"Andrew","age":25}';
Youcangoaheadanduseyournameandyourage,obviously,butmakesurethe
restlooksidenticaltowhatyouseehere.
Now,let'ssaywegettheearlier-definedJSONfromaserverorwegrabitfroma
textfile.Currently,it'suseless;ifwewanttogetthenamevalue,thereisnogood
way to do that because we're using a string, so personString.name doesn't exist.
Whatweneedtodoistakethestringandconvertitbackintoanobject.
Converting a string back to an
object
To convert the string back to object, we'll use the opposite of JSON.stringify,
which is JSON.parse. Let's make a variable to store the result. I'll create a person
variable and it will be set equal to JSON.parse, passing in as the one and only
argumentthestringyouwantto parse,in thiscase, theperson string, whichwe
definedearlier:
varperson=JSON.parse(personString);
Now,this variabletakes yourJSONand convertsitfromastringback intoits
originalform, whichcould beanarray oran object. Inour case, itconverts it
backintoanobject,andwehavethepersonvariableasanobject,asshowninthe
precedingcode.Also,wecanprovethatit'sanobjectusingthetypeofoperator.
I'lluseconsole.logtwice,justlikewedidpreviously.
Firstup, we'll printtypeof person, and then we'll printthe actual personvariable,
console.log(person):
console.log(typeofperson);
console.log(person);
Withthisinplace,wecannowrerunthecommandinTerminal;I'llactuallystart
nodemonandpassinjson.js:
nodemonjson.js
Asshowninthefollowingcodeoutput,youcannowseethatwe'reworkingwith
anobject,whichisgreat,andwehaveourregularobject:
WeknowthatAndrewisanobjectbecauseit'snotwrappedindoublequotes;the
valuesdon'thaveanyquotes,andweusesinglequotesforAndrew,whichisvalid
inJavaScript,butit'snotvalidinJSON.
Thisistheentireprocessoftakinganobject,convertingittoastring,andthen
takingthestringandconvertingitbackintotheobject,andthisisexactlywhat
we'lldointhenotesapp.Theonlydifferenceisthatwe'llbetakingthefollowing
stringandstoringitinafile,thenlateron,we'llbereadingthatstringfromthe
file using JSON.parse to convert it back to an object, as shown in the following
codeblock:
//varobj={
//name:'Andrew'
//};
//varstringObj=JSON.stringify(obj);
//console.log(typeofstringObj);
//console.log(stringObj);
varpersonString='{"name":"Andrew","age":25}';
varperson=JSON.parse{personString};
console.log(typeofperson);
console.log(person);
Storingthestringinafile
Withthebasicsinplace,let'stakeitjustonestepfurther,thatis,bystoringthe
stringinafile.Then,wewanttoreadthecontentsofthatfilebackbyusingthe
fs module and printing some properties from it. This means that we'll need to
convert the string that we get back from fs.readfilesync into an object using
JSON.parse.
Writing the file in the playground
folder
Let's go ahead and comment out all the code we have so far and start with a
cleanslate.Firstup,let'sgoaheadandloadinthefsmodule.Theconstvariable
fswillbesetequaltorequire,andwe'llpassthefsmodulethatwe'veusedinthe
past,asshownhere:
//varobj={
//name:'Andrew'
//};
//varstringObj=JSON.stringify(obj);
//console.log(typeofstringObj);
//console.log(stringObj);
//varpersonString='{"name":"Andrew","age":25}';
//varperson=JSON.parse(personString);
//console.log(typeofperson);
//console.log(person);
constfs=require('fs');
Thenextthingwe'lldoisdefinetheobject.Thisobjectwillbestoredinsideof
our file, and then will be read back and parsed. This object will be a variable
calledoriginalNote,andwe'llcallitoriginalNotebecauselateron,we'llloaditback
inandcallthatvariableNote.
Now,originalNote will be a regular JavaScript object with two properties. We'll
havethetitleproperty,whichwe'llsetequaltoSometitle,andthebodyproperty,
whichwewillsetequaltoSomebody,asshownhere:
varoriginalNote={
title:'Sometitle',
body:'Somebody'
};
The next step that you will need to do is take the original note and create a
variablecalledoriginalNoteString,andsetthatvariableequaltotheJSONvalueof
theobjectwedefinedearlier.Thismeansthatyou'llneedtouseoneofthetwo
JSONmethodsweusedpreviouslyinthissection.
Now, once you have that originalNoteString variable, we can write a file to the
filesystem.I'llwrite thatline foryou,fs.writeFileSync. The writeFileSyncmethod,
whichweusedbefore,takestwoarguments.Onewillbethefilename,andsince
we'reusingJSON,it'simportanttousetheJSONfileextension.I'llcallthisfile
notes.json. The other arguments will be text content, originalNoteString, which is
notyetdefined,asshowninthiscodeblock:
//originalNoteString
fs.writeFileSync('notes.json',originalNoteString);
This is the first step to the process; this is how we'll write that file into the
playground folder. The next step to the process will be to read out the contents,
parse it using the JSON method earlier, and print one of the properties to the
screentomakesurethatit'sanobject.Inthiscase,we'llprintthetitle.
Readingoutthecontentinthefile
Thefirststeptoprintthetitleistouseamethodwehaven'tusedyet.We'lluse
theread method available on the filesystem module to read the contents. Let's
make a variable called noteString. The noteString variable will be set equal to
fs.readFileSync.
Now, readFileSync is similar to writeFileSync except that it doesn't take the text
content, since it's gettingthe text content back for you. In this case, we'll just
specifythefirstargument,whichisthefilename,notes.JSON:
varnoteString=fs.readFileSync('notes.json');
Nowthatwehavethestring,itwillbeyourjobtotakethatstring,useoneofthe
precedingmethods,andconvertitbackintoanobject.Youcancallthatvariable
note.Nextup,theonlythinglefttodoistotestwhetherthingsareworkingas
expected,byprintingwiththehelpof console.log(typeof note). Then,below this,
we'lluseconsole.logtoprintthetitle,note.title:
//note
console.log(typeofnote);
console.log(note.title);
Now, over in Terminal, you can see (refer to the following screenshot) that I
havesaved the file in a broken state and it crashed, and that's expected when
you'reusingnodemon:
To resolve this, the first thing I'll do is fill out the originalNoteString variable,
which we had commented out earlier. It will now be a variable called
originalNoteString,andwe'llsetitequaltothereturnvaluefromJSON.stringify.
Now,weknowJSON.stringifytakesour regularobjectand itconverts theobject
intoastring.Inthiscase,we'lltaketheoriginalNoteobjectandconvertitintoa
string. The next line, which we already have filled out, will save that JSON
valueintothenotes.JSONfile.Thenwewillreadthatvalueout:
varoriginalNoteString=JSON.stringify(originalNote);
The next step will be to create the note variable. The note variable will be set
equaltoJSON.parse.
TheJSON.parsemethodtakesthestringJSONandconvertsitbackintoaregular
JavaScriptobjectorarray,dependingonwhateveryousave.Herewewillpassin
noteString,whichwe'llgetfromthefile:
varnote=JSON.parse(noteString);
With this in place, we are now done. When I save this file, nodemon will
automaticallyrestartandwewouldexpecttonotseeanerror.Instead,weexpect
thatwe'llseetheobjecttypeaswellasthenotetitle.RightinsideTerminal,we
haveobjectandSometitleprintingtothescreen:
Withthis inplace,we'vesuccessfullycompleted thechallenge. Thisisexactly
howwewillsaveournotes.
Whensomeoneaddsanewnote,we'llusethefollowingcodetosaveit:
varoriginalNote={
title:'Sometitle',
body:'Somebody'
};
varoriginalNoteString=JSON.stringify(originalNote);
fs.writeFileSync('notes.json',originalNoteString);
Whensomeonewantstoreadtheirnote,we'llusethefollowingcodetoreadit:
varnoteString=fs.readFileSync('notes.json');
varnote=JSON.parse(noteString);
console.log(typeofnote);
console.log(note.title);
Now,whatifsomeonewantstoaddanote?Thiswillrequireustofirstreadall
of the notes, then modify the notes array, and then use the code (refer to the
previouscodeblock)tosavethenewarraybackintothefilesystem.
Ifyouopenupthatnotes.JSONfile,youcanseerightherethatwehaveourJSON
codeinsidethefile:
.jsonisactuallyafileformatthat'ssupportedbymosttexteditors,soIactually
alreadyhave some nice syntax highlighting built in. Now, in the next section,
we'll befilling outtheaddNote function using the exact same logic that we just
usedinsideofthissection.
Addingandsavingnotes
Intheprevioussection,youlearnedhowtoworkwithJSONinsideNode.js,and
thisistheexactformatwe'llbeusingforthenotes.jsapplication.Whenyoufirst
runacommand,we'llloadinallthenotes thatmightalready exist.Then we'll
run the command, whether it's adding, removing, or reading notes. Finally, if
we'veupdatedthearray,likewewillwhenweaddandremovenotes,we'llsave
thosenewnotesbackintotheJSONfile.
Now,thiswillallhappeninsideoftheaddNotefunction,whichwedefinedinthe
notes.jsapplication,andwealreadywiredupthisfunction.Inearliersections,we
ran the app add command, and this function executed with the title and body
arguments.
Addingnotes
Togetstartedwithaddingnotes,thefirstthingwe'lldoiscreateavariablecalled
notes, and for the moment, we'll set it equal to an empty array, just as in the
following,usingoursquarebrackets:
varaddNote=(title,body)=>{
varnotes=[];
};
Nowthatwehavetheemptyarray,wecangoaheadandmakeavariablecalled
note,whichistheindividualnote.Thiswillrepresentthenewnote:
varaddNote=(title,body)=>{
varnotes=[];
varnote={

}
};
Onthatnote,we'llhavethetwoproperties:atitleandabody.Now,titlecanbe
set equal to the title variable, but, as we know, inside ES6, we can simply
removeitwhenbothvaluesare thesame;so we'lladd titleandbody as shown
here:
varaddNote=(title,body)=>{
varnotes=[];
varnote={
title,
body
};
};
Nowwehavethenoteandthenotesarray.
Addingnotestothenotesarray
Thenextstepintheprocessofaddingnoteswillbetoaddthenotetothenotes
array.Thenotes.pushmethodwillletusdojustthat.Thepushmethodonanarray
lets you passin an item, which gets added tothe end of the array, and in this
case,we'llpassinthenoteobject. Sowe havean emptyarray,andwe addour
oneitem,asshowninthefollowingcode;next,wepushitin,whichmeansthat
wehaveanarraywithoneitem:
varaddNote=(title,body)=>{
varnotes=[];
varnote={
title,
body
};
notes.push(note);
};
Thenextstepintheprocesswillbetoupdatethefile.Now,wedon'thaveafile
inplace,butwecanloadanfsfunctionandstartcreatingthefile.
Up above the addNote function, let's load in the fs module. I'll create a const
variable called fs and set it equal to the return result from require, and we'll
requirethefsmodule,whichisacorenodemodule,sothere'snoneedtoinstall
itusingNPM:
constfs=require('fs');
Withthisinplace,wecantakeadvantageoffsinsidetheaddNotefunction.
Right after we push our item on to the notes array, we'll call fs.writeFileSync,
whichwe'veusedbefore.Weknowweneedtopassintwothings:thefilename
andthe content we want to save. For the file, I'll call,notes-data.JSON, and then
we'll pass in the content to save, which in this case will be the stringifynotes
array,whichmeanswecancallJSON.stringifypassinginnotes:
notes.push(note);
fs.writeFileSync('notes-data.json',JSON.stringify(notes));
WecouldhavebrokenJSON.stringfy(notes)outintoitsownvariable
andreferencedthevariableintheabovestatement,butsincewe'll
onlybeusingitinoneplace,Ifindthisisthebettersolution.
At this point, when we add a new note, it will update the notes-data.JSON file,
whichwillbecreatedonthemachinesinceitdoesnotexist,andthenotewillsit
inside it. Now, it's important to note that currently every time you add a new
note,itwillwipeallexistingonesbecause weneverloadin theexistingones,
butwecangetstartedtestingthatthisnoteworksasexpected.
I'll save the file, and over inside of Terminal, we can run this file using node
app.js.Sincewewanttoaddanote,wewillbeusingthataddcommandwhichwe
setup,thenwe'llspecifyourtitleandourbody.Thetitleflagcangetsetequal
tosecret,andforthebodyflag,I'llsetitequaltotheSomebodyherestring,asshown
here:
nodeapp.jsadd--title=secret--body="Somebodyhere"
Now,whenwerunthiscommandfromTerminal,we'llseewhatwe'dexpect:
Asshownintheprecedingscreenshot,weseeacoupleofthefilecommandswe
added: we see that the add command was executed, and we have our Yargs
arguments.Thetitleandbodyargumentsalsoshowup.InsideAtom,wealsosee
thatwehaveanewnotes-data.jsonfile,andinthefollowingscreenshot,wehave
ournote,withthesecrettitleandtheSomebodyherebody:
ThisisthefirststepinwiringupthataddNotefunction.Wehaveanexistingnotes
fileandwedowanttotakeadvantageofthesenotes.Ifnotesalreadyexist,we
don't want to simply wipe them every time someone adds a new note. This
meansthatinnotes.js,earlieratthebeginningoftheaddNotefunction,we'llfetch
thosenotes.
Fetchingnewnotes
I'lladdcodeforfetchingnewnoteswhereIdefinethenotesandnotevariables.
As shown in the following code, we'll use fs.readFileSync, which we've already
explored.Thiswilltakethefilename,inour case,notes-data.JSON.Now, wewill
want to store the return value from readFileSync on a variable; I'll call that
variable,notesString:
varnotesString=fs.readFileSync('notes-data.json');
Since this is the string version, we haven't passed it through the JSON.parse
method. So, Ican set notes (the variable we defined earlierin addNote function)
equaltothereturnvaluefromtheJSON.parsemethod.ThenJSON.parsewilltakethe
stringfromthefilewereadanditwillparseitintoanarray;wecouldpassin
notesStringjustlikethis:
notes=JSON.parse(notesString);
With this in place, adding a new note is no longer going to remove all of the
notesthatwerealreadythere.
OverinTerminal,I'llusetheuparrowkeytoloadinthelastcommand,andI'll
navigateovertothetitleflagandchangeittosecret2andrerunthecommand:
nodeapp.jsadd--title=secret2--body="Somebodyhere"
InAtom,thistimeyoucanseewenowhavetwonotesinsideofourfile:
We have an arraywith two objects; thefirst one has the titleof secretandthe
secondonehasthetitleofsecret2,whichisbrilliant!
Tryingandcatchingcodeblock
Now,ifthenotes-data.jsonfiledoesnotexist,whichitwon'twhentheuserfirst
runs the command, the program will crash, as shown in the following code
output.Wecanprovethisbysimplyrerunningthelastcommandafterdeleting
thenote-data.JSONfile:
Righthere,youcanseewe'reactuallygettingaJavaScripterror,nosuchfileor
directory;it'stryingtoopenupthenotes-data.JSONfile,butwithoutmuchsuccess.
To fix this, we'll use a try-catch statement from JavaScript, which hopefully
you'veseeninthepast.Tobrushupthis,let'sgooveritreallyquick.
To createa try-catch statement, all you do is you type try, which is a reserved
keyword, and then you open and close a set of curly braces. Inside the curly
bracesisthecodethatwillrun.Thisisthecodethatmayormaynotthrowan
error. Next, you'll specify the catch block. Now, the catch block will take an
argument,anerrorargument,anditalsohasacodeblockthatruns:
try{
}catch(e){
}
Thiscodewillrunifandonlyifoneofyourerrorsintryactuallyoccurs.So,if
weloadthefileusingreadFileSyncandthefileexists,that'sfine,catchblock will
never run. Ifit fails, catch block will run and we can do something to recover
fromthaterror.Withthisinplace,allwe'lldoismovethenoteStringvariableand
theJSON.parsestatementsintotry,asshownhere:
try{
varnotesString=fs.readFileSync('notes-data.json');
notes=JSON.parse(notesString);
}catch(e){
}
That'sit;nothingelseneedstohappen.Wedon'tneedtoputanycodeincatch,
although you do need todefine the catch block. Now, let's take a look at what
happenswhenwerunthewholecode.
The first thing that happens is that we create our static variables—nothing
specialthere—thenwetrytoloadinthefile.IfthenotesStringfunctionfails,that
isfinebecausewealreadydefinednotestobeanemptyarray.Ifthefiledoesn't
exist and it fails, then we probably want an empty array for notes anyways,
becauseclearlytherearenonotes,andthere'snofile.
Nextup,we'llparsethatdataintonotes.Thereisa chancethatthiswillfailif
there'sinvaliddatainthenotes-data.JSONfile,sothetwolinescanhaveproblems.
Byputtingthemintry-catch,we'rebasicallyguaranteeingthattheprogramisn't
goingtoworkunexpectedly,whetherthefiledoesordoesn'texist,butitcontains
corrupteddata.
With this in place, we can now save notes and rerun that previous command.
NotethatIdonothavethenotes-datafileinplace.WhenIrunthecommand,we
don'tseeanyerrors,everythingseemstorunasexpected:
WhenyounowvisitAtom,youcanseethatthenotes-datafiledoesindeedexist,
andthedatainsideitlooksgreat:
Thisisallweneedtodotofetchthenotes,updatethenoteswiththenewnote,
andfinallysavethenotestothescreen.
Now, there is still a slight problem with addNote. Currently, addNote allows for
duplicate titles; I could already have a note in the JSON file with the title of
secret.Icancomealongandtrytoaddanewnotewiththetitleofsecretandit
willnotthrowanerror.WhatI'dliketodoistomakethetitleunique,sothatif
there'salreadyanotewiththattitle,itwillthrowanerror,lettingyouknowthat
youneedtocreateanotewithadifferenttitle.
Makingthetitleunique
The first step to makethe title unique will be to loop through all of the notes
afterwe load themin and check whether there areany duplicates. If there are
duplicates,we'llnotcallthefollowingtwolines:
notes.push(note);
fs.writeFileSync('notes-data.json',JSON.stringify(notes));
Iftherearenoduplicatesthenit'sfine,wewillcallbothofthelinesshowninthe
precedingcodeblock,updatingthenotes-datafile.
Now,we'llberefactoringthisfunctiondowntheline.Thingsaregettingalittle
wonky and a little out of control, but for the moment, we can add this
functionalityrightintothe function.Let'sgoahead andmakea variablecalled
duplicateNotes.
TheduplicateNotesvariablewilleventuallystoreanarraywithallofthenotesthat
alreadyexistinsidethenotesarraythathavethetitleofthenoteyou'retryingto
create.Now,thismeansthatiftheduplicateNotesarrayhasanyitems,that'sbad.
This means that the note already exists and we should not add the note. The
duplicateNotesvariablewillget setequalto acalltonotes, whichis ourarray of
notes.filter:
varduplicateNotes=notes.filter();
Thefiltermethodisanarraymethodthattakesa callback.We'lluseanarrow
function,andthatcallbackwillgetcalledwiththeargument.Inthiscase,itwill
be the singular version; if I have an array of notes, it will be called with an
individualnote:
varduplicateNotes=notes.filter((note)=>{
});
This function gets called once for every item in the array, and you have the
opportunitytoreturneithertrueorfalse.Ifyoureturntrue,itwillkeepthatitem
in the array, which will eventually get saved into duplicateNotes. If you return
false, the new array it generates will not have that item inside duplicateNotes
variable.Allwewanttodoistoreturntrueifthetitlesmatch,whichmeansthat
wecanreturnnote.title===title,asshownhere:
varduplicateNotes=notes.filter((note)=>{
returnnote.title===title;
});
Ifthetitlesareequal,thentheprecedingreturnstatementwillresultastrueand
theitemwillbekeptinthearray,whichmeansthatthereareduplicatenotes.If
thetitlesarenotequal,whichismostlikelythecase,thestatementwillresultas
false,whichmeansthattherearenoduplicatenotes.Now,wecansimplifythisa
littlemoreusingarrowfunctions.
Arrow functions actually allow you to remove the curly braces if
youonlyhaveonestatement.
I'llusethearrowfunction,asshownhere:
varduplicateNotes=notes.filter((note)=>note.title===title);
Here,Ihavedeletedeverythingexceptnote.title===titleandaddedthisinfront
ofthearrowfunctionsyntax.
ThisisperfectlyvalidusingES6arrowfunctions.Youhaveyourargumentson
theleft, the arrow, and on the right, you have one expression. The expression
doesn'ttake a semicolon and it's automatically returned as the function result.
Thismeansthat thecode wehave hereis identicalto thecode we hadearlier,
onlyit'smuchsimpleranditonlytakesuponeline.
Now that we have this inplace, we can go ahead and check the length of the
duplicateNotesvariable.IfthelengthofduplicateNotesisgreaterthan0,thismeans
thatwedon'twanttosavethenotebecauseanotealreadyexistswiththattitle.If
itis0,we'llsavethenote.
if(duplicateNotes.length===0){
}
Here,insidetheifcondition,we'recomparingthenoteslengthwiththenumber
zero.Iftheyareequal,thenwedowanttopushthenoteontothenotesarrayand
savethefile.I'llcutthefollowingtwolines:
notes.push(note);
fs.writeFileSync('notes-data.json',JSON.stringify(notes));
Let'spastethemrightinsideoftheifstatement,asshownhere:
if(duplicateNotes.length===0){
notes.push(note);
fs.writeFileSync('notes-data.json',JSON.stringify(notes));
}
Ifthey'renotequal,that'sokaytoo;inthatcasewe'lldonothing.
Withthisinplace,wecannowsaveourfileandtestthisfunctionalityout.We
haveournotes-data.jsonfile,andthisfilealreadyhasanotewithatitleofsecret2.
Let'srerunthepreviouscommandtotrytoaddanewnotewiththatsametitle:
nodeapp.jsadd--title=secret2--body="Somebodyhere"
You'reinTerminal,sowe'llheadbackintoourJSONfile.Youcanseerighthere
thatwestilljusthaveonenote:
Nowallthetitlesinsideofourapplicationwillbeunique,sowecanusethese
titlestofetchanddeletenotes.
Let'sgo aheadandtest thatothernotes canstillbe added.I'llchange thetitle
flagfromsecret2tosecret,andrunthatcommand:
nodeapp.jsadd--title=secret--body="Somebodyhere"
Insideournotes-datafile,youcanseebothnotesshowup:
AsImentionedearlier,nextwewillbedoingsomerefactoring,sincethecode
thatloadsthefile,andthecodethatsavesthefile,willbothbeusedinmostof
thefunctionswehavedefinedand/orwilldefine(thatis,thegetAll,getNoteand
removeNotefunctions).
Refactoring
In the previous section, you created the addNote function, which works well. It
starts by creating some static variables, then we fetch any existing notes, we
checkforduplicates,andiftherearenone,wepushitontothelist,andthenwe
savethedatabackintothefilesystem.
Theonlyproblemisthatwe'llbedoingalotofthesestepsoverandoveragain
foreverymethod.Forexample,withgetAll,theideaistofetchallofthenotes,
andsendthembacktoapp.jssoitcanprintthemtothescreenfortheuser.The
firstthingwe'lltodoinsideofthegetAllstatementishavethesamecode;we'll
haveourtry-catchblocktofetchtheexistingnotes.
Now, this is a problem because we'll be repeating code throughout the
application.Itwillbebesttobreak outthe fetchingofnotes andthe savingof
notesintoseparatefunctionsthatwecancallinmultiplelocations.
Moving functionality into
individualfunctions
Toresolvetheproblem,I'dliketogetstartedbycreatingtwonewfunctions:
fetchNotes
saveNotes
Thefirstfunction,fetchNotes,willbeanarrowfunction,anditwillnottotakeany
argumentssinceitwillbefetchingnotesfromthefilesystem,asshownhere:
varfetchNotes=()=>{
};
Thesecondfunction,saveNotes,willneedtotakeanargument.Itwillneedtotake
thenotesarrayyouwanttosavetothefilesystem.We'llsetitequaltoanarrow
function,andthenwe'llprovideourargument,whichIwillnamenotes,asshown
here:
varsaveNotes=(notes)=>{
};
Nowthatwehavethesetwofunctions,wecangoaheadandstartmovingsome
ofthefunctionalityfromaddNoteupintotheindividualfunctions.
WorkingwithfetchNotes
Firstup,let'sdofetchNotes,whichwillneedthefollowingtry-catchblock.
I'llactuallycutitoutofaddNoteandpasteitinthefetchNotesfunction,asshown
here:
varfetchNotes=()=>{
try{
varnotesString=fs.readFileSync('notes-data.json');
notes=JSON.parse(notesString);
}catch(e){
}
};
This alone isnotenough, because currently we don't return anything fromthe
function.Whatwewanttodoistoreturnthenotes.Thismeansthatinsteadof
saving the result from JSON.parse onto the notes variable, which we haven't
defined,we'llsimplyreturnittothecallingfunction,asshownhere:
varfetchNotes=()=>{
try{
varnotesString=fs.readFileSync('notes-data.json');
returnJSON.parse(notesString);
}catch(e){
}
};
So,ifIcallfetchNotesintheaddNotefunction,shownasfollows,Iwillgetthenotes
arraybecauseofthereturnstatementintheprecedingcode.
Now,iftherearenonotes,maybethere'snofileatall;orthereisafile,butthe
dataisn'tJSON,wecanreturnanemptyarray.We'lladdareturnstatementinside
ofcatch,asshowninthefollowingcodeblock,becauseremember,catchrunsif
anythinginsidetryfails:
varfetchNotes=()=>{
try{
varnotesString=fs.readFileSync('notes-data.json');
returnJSON.parse(notesString);
}catch(e){
return[];
}
};
Now,thisletsussimplifyaddNoteevenfurther.Wecanremovetheemptyspace
and we can take the array that we set on the notes variable and remove it and
insteadcallfetchNotes,asshownhere:
varaddNote=(title,body)=>{
varnotes=fetchNotes();
varnote={
title,
body
};
Withthisinplace,wenowhavetheexactsamefunctionalitywehadbefore,but
wehaveareusablefunction,fetchNotes,whichwecanuseintheaddNotefunction
tohandletheothercommandsthatourappwillsupport.
Instead of copying code and having it in multiple places in your file, we've
brokenitintooneplace.Ifweeverwanttochangehowthisfunctionalityworks,
whetherwewanttochangethefilenameorsomeofthelogicsuchasthetry-catch
block,wecanchangeitonceinsteadofhavingtochangeitineveryfunctionwe
have.